{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/mnt/workspace/mdy/miniforge/envs/mdy/lib/python3.10/site-packages/_distutils_hack/__init__.py:53: UserWarning: Reliance on distutils from stdlib is deprecated. Users must rely on setuptools to provide the distutils module. Avoid importing distutils or import setuptools first, and avoid setting SETUPTOOLS_USE_DISTUTILS=stdlib. Register concerns at https://github.com/pypa/setuptools/issues/new?template=distutils-deprecation.yml\n",
      "  warnings.warn(\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "import triton\n",
    "import triton.language as tl\n",
    "from causal_conv1d import causal_conv1d_fn\n",
    "import os\n",
    "os.environ['TRITON_PRINT_AUTOTUNING'] = '1'\n",
    "# from mamba_ssm import mamba_inner_fn\n",
    "from copy import deepcopy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# v1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# @triton.autotune([triton.Config({\"BLOCK_SIZE_L\":bsl, \"BLOCK_SIZE_D\":bsd}, num_stages=ns, num_warps=nw)\n",
    "#                                  for bsl in [32, 64, 128]\n",
    "#                                  for bsd in [32, 64, 128]\n",
    "#                                  for ns in [1,2,3,4]\n",
    "#                                  for nw in [4,8]], key=['L', 'D'])\n",
    "@triton.jit\n",
    "def _conv1d_fwd_kernel(X, W, Y, BIAS, HAVE_BIAS,\n",
    "                       stride_xb, stride_xd, stride_xl,\n",
    "                       stride_yb, stride_yd, stride_yl,\n",
    "                       stride_wd, stride_wk,\n",
    "                       B, D, L, K, \n",
    "                       BLOCK_SIZE_L:tl.constexpr=64, BLOCK_SIZE_D:tl.constexpr=128,\n",
    "                       num_stages: tl.constexpr=3,\n",
    "                        ):\n",
    "    off_b = tl.program_id(0)\n",
    "    off_d = tl.program_id(1) * BLOCK_SIZE_D\n",
    "    off_l = tl.program_id(2) * BLOCK_SIZE_L\n",
    "\n",
    "    X += off_b * stride_xb \n",
    "    Y += off_b * stride_yb\n",
    "    # kk = tl.arange(0, BLOCK_SIZE_K)\n",
    "    dd = tl.arange(0, BLOCK_SIZE_D)\n",
    "    ll = tl.arange(0, BLOCK_SIZE_L)\n",
    "    rows = off_d + dd\n",
    "    cols = off_l + ll - K\n",
    "    x_ptrs = X + rows[:, None] * stride_xd + cols[None, :] * stride_xl \n",
    "    \n",
    "    w_ptrs = W + rows * stride_wd\n",
    "    row_mask = rows < D\n",
    "\n",
    "    acc = tl.zeros((BLOCK_SIZE_D, BLOCK_SIZE_L), dtype=tl.float32)\n",
    "    for _ in tl.range(K):\n",
    "        w = tl.load(w_ptrs, mask=row_mask, other=0.).to(tl.float32)\n",
    "        cols += 1\n",
    "        x_ptrs += stride_xl\n",
    "        col_mask = (cols >= 0) & (cols < L)\n",
    "        x = tl.load(x_ptrs, mask=col_mask[None, :] & row_mask[:, None], other=0.).to(tl.float32)\n",
    "        acc += x * w[:, None]\n",
    "        w_ptrs += stride_wk \n",
    "    if HAVE_BIAS:\n",
    "        bias = tl.load(BIAS+rows, mask=row_mask, other=0.).to(tl.float32)\n",
    "        acc += bias[:, None]\n",
    "    y_ptrs = Y + rows[:, None] * stride_yd + (off_l + ll)[None, :] * stride_yl\n",
    "    col_mask = off_l + ll<L\n",
    "    tl.store(y_ptrs, acc, mask=col_mask[None, :] & row_mask[:, None])\n",
    "\n",
    "# @triton.autotune([triton.Config({\"BLOCK_SIZE_D\":bsd}, num_stages=ns, num_warps=nw)\n",
    "#                                 #  for bsl in [32, 64, 128]\n",
    "#                                  for bsd in [16, 32, 64, 128]\n",
    "#                                  for ns in [1,2,3,4]\n",
    "#                                  for nw in [4,8]], key=['L', 'D'])\n",
    "@triton.jit\n",
    "def _conv1d_bwd_kernel(DY, DX, DW,\n",
    "                        X, W,\n",
    "                       stride_dyb, stride_dyd, stride_dyl,\n",
    "                       stride_dxb, stride_dxd, stride_dxl,\n",
    "                       stride_xb, stride_xd, stride_xl,\n",
    "                       stride_wd, stride_wk,\n",
    "                       stride_dwb, stride_dwd, stride_dwk,\n",
    "                       B, D, L, K,\n",
    "                       BLOCK_SIZE_L:tl.constexpr, BLOCK_SIZE_D:tl.constexpr=16,\n",
    "                       num_warps:tl.constexpr=4, num_stages: tl.constexpr=4,\n",
    "                        ):\n",
    "    off_b = tl.program_id(0)\n",
    "    off_d = tl.program_id(1) * BLOCK_SIZE_D\n",
    "    off_l = tl.program_id(2) * BLOCK_SIZE_L\n",
    "    b = tl.cdiv(L, BLOCK_SIZE_L)\n",
    "\n",
    "    X += off_b * stride_xb \n",
    "    DY += off_b * stride_dyb\n",
    "    DX += off_b * stride_dxb\n",
    "    DW += (off_b * b + tl.program_id(2)) * stride_dwb\n",
    "    dd = tl.arange(0, BLOCK_SIZE_D)\n",
    "    ll = tl.arange(0, BLOCK_SIZE_L)\n",
    "    rows = off_d + dd\n",
    "    cols = off_l + ll + K\n",
    "    col_mask_x = off_l + ll < L\n",
    "    x_ptrs = X + rows[:, None] * stride_xd + (off_l + ll)[None, :] * stride_xl \n",
    "    dx_ptrs = DX + rows[:, None] * stride_dxd + cols[None, :] * stride_dxl\n",
    "    dy_ptrs = DY + rows[:, None] * stride_dyd + cols[None, :] * stride_dyl\n",
    "    w_ptrs = W + rows * stride_wd\n",
    "    dw_ptrs = DW + rows * stride_dwd\n",
    "    row_mask = rows < D\n",
    "\n",
    "    x = tl.load(x_ptrs, mask=col_mask_x[None, :] & row_mask[:, None], other=0.).to(tl.float32)\n",
    "    acc_dx = tl.zeros((BLOCK_SIZE_D, BLOCK_SIZE_L), dtype=tl.float32)\n",
    "    for idx in tl.range(K):\n",
    "        w = tl.load(w_ptrs, mask=row_mask, other=0.).to(tl.float32)\n",
    "        cols -= 1\n",
    "\n",
    "        dy_ptrs -= stride_dyl\n",
    "        col_mask = (cols >= 0) & (cols < L)\n",
    "        \n",
    "        dy = tl.load(dy_ptrs, mask=col_mask[None, :] & row_mask[:, None], other=0.).to(tl.float32)\n",
    "        acc_dx += dy * w[:, None]\n",
    "        dw = tl.sum(dy * x, 1)\n",
    "        tl.store(dw_ptrs, dw, mask=row_mask)\n",
    "        w_ptrs += stride_wk\n",
    "        dw_ptrs += stride_dwk\n",
    "\n",
    "    dx_ptrs = DX + rows[:, None] * stride_dxd + cols[None, :] * stride_dxl\n",
    "    tl.store(dx_ptrs, acc_dx, mask=col_mask_x[None, :] & row_mask[:, None])\n",
    "\n",
    "class _TritonCausalConv1dFunction(torch.autograd.Function):\n",
    "    @staticmethod\n",
    "    def forward(ctx, x, weight, bias=None):\n",
    "        # print(weight.shape, x.shape)\n",
    "        D, K = weight.shape\n",
    "        B, D, L = x.shape\n",
    "        y = torch.empty_like(x)\n",
    "        HAVE_BIAS = True \n",
    "        ctx.bias = bias\n",
    "        if bias is None:\n",
    "            bias = weight\n",
    "            HAVE_BIAS = False\n",
    "        grid = lambda meta: (B, triton.cdiv(D, meta['BLOCK_SIZE_D']), triton.cdiv(L,meta['BLOCK_SIZE_L']))\n",
    "        _conv1d_fwd_kernel[grid](x, weight, y, bias, HAVE_BIAS,\n",
    "                                *x.stride(),\n",
    "                                *y.stride(),\n",
    "                                *weight.stride(),\n",
    "                                B, D, L, K,\n",
    "                                )\n",
    "        \n",
    "        ctx.save_for_backward(x, weight)\n",
    "        return y\n",
    "    \n",
    "    @staticmethod\n",
    "    def backward(ctx, dy):\n",
    "        x, weight = ctx.saved_tensors\n",
    "        D, K = weight.shape\n",
    "        B, D, L = x.shape\n",
    "        BLOCK_SIZE_L = 128\n",
    "        # a = triton.cdiv(D, BLOCK_SIZE_D)\n",
    "        b = triton.cdiv(L, BLOCK_SIZE_L)\n",
    "        dx = torch.empty_like(dy)\n",
    "        dw = torch.empty(B*b, D, K, dtype=x.dtype, device=x.device)\n",
    "        grid = lambda meta: (B, triton.cdiv(D, meta['BLOCK_SIZE_D']), b)\n",
    "        _conv1d_bwd_kernel[grid](dy, dx, dw,\n",
    "                                x, weight,\n",
    "                                *dy.stride(),\n",
    "                                *dx.stride(),\n",
    "                                *x.stride(),\n",
    "                                *weight.stride(),\n",
    "                                *dw.stride(),\n",
    "                                B, D, L, K,\n",
    "                                BLOCK_SIZE_L, \n",
    "                                # BLOCK_SIZE_D,\n",
    "                                # num_warps=4, num_stages=1\n",
    "                                )\n",
    "        dw = dw.sum(0)\n",
    "        dbias = None\n",
    "        if ctx.bias is not None:\n",
    "            dbias = dy.transpose(-1,-2).reshape(-1, D).sum(0)\n",
    "        return dx, dw, dbias\n",
    "    \n",
    "triton_causal_conv1d = _TritonCausalConv1dFunction.apply"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# v2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# @triton.autotune([triton.Config({\"BLOCK_SIZE_L\":bsl, \"BLOCK_SIZE_D\":bsd}, num_stages=ns, num_warps=nw)\n",
    "#                                  for bsl in [32, 64, 128]\n",
    "#                                  for bsd in [32, 64, 128]\n",
    "#                                  for ns in [1,2,3,4]\n",
    "#                                  for nw in [4,8]], key=['L', 'D'])\n",
    "@triton.jit\n",
    "def _conv1d_fwd_kernel2(X, W, Y, BIAS, HAVE_BIAS,\n",
    "                       stride_xb, stride_xd, stride_xl,\n",
    "                       stride_yb, stride_yd, stride_yl,\n",
    "                       stride_wd, stride_wk,\n",
    "                       B, D, L, K, \n",
    "                       BLOCK_SIZE_L:tl.constexpr=64, BLOCK_SIZE_D:tl.constexpr=128,\n",
    "                       num_stages: tl.constexpr=3,\n",
    "                        ):\n",
    "    off_b = tl.program_id(0)\n",
    "    off_d = tl.program_id(1) * BLOCK_SIZE_D\n",
    "    off_l = tl.program_id(2) * BLOCK_SIZE_L\n",
    "\n",
    "    X += off_b * stride_xb \n",
    "    Y += off_b * stride_yb\n",
    "    # kk = tl.arange(0, BLOCK_SIZE_K)\n",
    "    dd = tl.arange(0, BLOCK_SIZE_D)\n",
    "    ll = tl.arange(0, BLOCK_SIZE_L)\n",
    "    rows = off_d + dd\n",
    "    cols = off_l + ll - K\n",
    "    x_ptrs = X + rows[:, None] * stride_xd + cols[None, :] * stride_xl \n",
    "    \n",
    "    w_ptrs = W + rows * stride_wd\n",
    "    row_mask = rows < D\n",
    "\n",
    "    acc = tl.zeros((BLOCK_SIZE_D, BLOCK_SIZE_L), dtype=tl.float32)\n",
    "    for _ in tl.range(K):\n",
    "        w = tl.load(w_ptrs, mask=row_mask, other=0.).to(tl.float32)\n",
    "        cols += 1\n",
    "        x_ptrs += stride_xl\n",
    "        col_mask = (cols >= 0) & (cols < L)\n",
    "        x = tl.load(x_ptrs, mask=col_mask[None, :] & row_mask[:, None], other=0.).to(tl.float32)\n",
    "        acc += x * w[:, None]\n",
    "        w_ptrs += stride_wk \n",
    "    if HAVE_BIAS:\n",
    "        bias = tl.load(BIAS+rows, mask=row_mask, other=0.).to(tl.float32)\n",
    "        acc += bias[:, None]\n",
    "    y_ptrs = Y + rows[:, None] * stride_yd + (off_l + ll)[None, :] * stride_yl\n",
    "    col_mask = off_l + ll<L\n",
    "    tl.store(y_ptrs, acc, mask=col_mask[None, :] & row_mask[:, None])\n",
    "\n",
    "@triton.autotune([triton.Config({\"BLOCK_SIZE_D\":bsd}, num_stages=ns, num_warps=nw)\n",
    "                                #  for bsl in [32, 64, 128]\n",
    "                                 for bsd in [16, 32, 64, 128]\n",
    "                                 for ns in [1,2,3,4]\n",
    "                                 for nw in [4,8]], key=['L', 'D'])\n",
    "@triton.jit\n",
    "def _conv1d_bwd_kernel2(DY, DX, DW,\n",
    "                        X, W,\n",
    "                       stride_dyb, stride_dyd, stride_dyl,\n",
    "                       stride_dxb, stride_dxd, stride_dxl,\n",
    "                       stride_xb, stride_xd, stride_xl,\n",
    "                       stride_wd, stride_wk,\n",
    "                       stride_dwb, stride_dwd, stride_dwk,\n",
    "                       B, D, L, K,\n",
    "                       BLOCK_SIZE_L:tl.constexpr, BLOCK_SIZE_K:tl.constexpr, \n",
    "                       BLOCK_SIZE_D:tl.constexpr=16,\n",
    "                       num_warps:tl.constexpr=4, num_stages: tl.constexpr=4,\n",
    "                        ):\n",
    "    off_b = tl.program_id(0)\n",
    "    off_d = tl.program_id(1) * BLOCK_SIZE_D\n",
    "    off_l = tl.program_id(2) * BLOCK_SIZE_L\n",
    "    b = tl.cdiv(L, BLOCK_SIZE_L)\n",
    "\n",
    "    X += off_b * stride_xb \n",
    "    DY += off_b * stride_dyb\n",
    "    DX += off_b * stride_dxb\n",
    "    DW += (off_b * b + tl.program_id(2)) * stride_dwb\n",
    "    dd = tl.arange(0, BLOCK_SIZE_D)\n",
    "    ll = tl.arange(0, BLOCK_SIZE_L)\n",
    "    kk = tl.arange(0, BLOCK_SIZE_K)\n",
    "\n",
    "    rows = off_d + dd\n",
    "    cols = off_l + ll\n",
    "    col_mask = cols < L\n",
    "    x_ptrs = X + rows[:, None] * stride_xd + cols[None, :] * stride_xl \n",
    "    dx_ptrs = DX + rows[:, None] * stride_dxd + cols[None, :] * stride_dxl\n",
    "    k_mask = kk < K\n",
    "    tmp = cols[:, None] + BLOCK_SIZE_K - 1 - kk[None, :] \n",
    "    dy_ptrs = DY + rows[:, None, None] * stride_dyd + tmp[None, :, :] * stride_dyl\n",
    "    dy_mask = (tmp >= 0) & (tmp < L) & k_mask[None, :]\n",
    "    w_ptrs = W + rows[:, None] * stride_wd + kk[None, :] * stride_wk\n",
    "    dw_ptrs = DW + rows[:, None] * stride_dwd + kk[None, :] * stride_dwk\n",
    "    row_mask = rows < D\n",
    "    \n",
    "    x = tl.load(x_ptrs, mask=col_mask[None, :] & row_mask[:, None], other=0.).to(tl.float32)\n",
    "    w = tl.load(w_ptrs, mask=row_mask[:, None] & k_mask[None, :], other=0.).to(tl.float32)\n",
    "    \n",
    "    dy = tl.load(dy_ptrs, mask=dy_mask[None, :, :] & row_mask[:, None, None], other=0.).to(tl.float32)\n",
    "    dx = tl.sum(dy * w[:, None, :], 2)\n",
    "    dw = tl.sum(dy * x[:, :, None], 1)\n",
    "    \n",
    "    tl.store(dw_ptrs, dw, mask=row_mask[:,None] & k_mask[None, :])\n",
    "    tl.store(dx_ptrs, dx, mask=col_mask[None, :] & row_mask[:, None])\n",
    "\n",
    "class _TritonCausalConv1dFunction2(torch.autograd.Function):\n",
    "    @staticmethod\n",
    "    def forward(ctx, x, weight, bias=None):\n",
    "        # print(weight.shape, x.shape)\n",
    "        D, K = weight.shape\n",
    "        B, D, L = x.shape\n",
    "        y = torch.empty_like(x)\n",
    "        HAVE_BIAS = True \n",
    "        ctx.bias = bias\n",
    "        if bias is None:\n",
    "            bias = weight\n",
    "            HAVE_BIAS = False\n",
    "        grid = lambda meta: (B, triton.cdiv(D, meta['BLOCK_SIZE_D']), triton.cdiv(L,meta['BLOCK_SIZE_L']))\n",
    "        _conv1d_fwd_kernel[grid](x, weight, y, bias, HAVE_BIAS,\n",
    "                                *x.stride(),\n",
    "                                *y.stride(),\n",
    "                                *weight.stride(),\n",
    "                                B, D, L, K,\n",
    "                                )\n",
    "        \n",
    "        ctx.save_for_backward(x, weight)\n",
    "        return y\n",
    "    \n",
    "    @staticmethod\n",
    "    def backward(ctx, dy):\n",
    "        x, weight = ctx.saved_tensors\n",
    "        D, K = weight.shape\n",
    "        B, D, L = x.shape\n",
    "        BLOCK_SIZE_L = 64\n",
    "        # a = triton.cdiv(D, BLOCK_SIZE_D)\n",
    "        b = triton.cdiv(L, BLOCK_SIZE_L)\n",
    "        dx = torch.empty_like(dy)\n",
    "        dw = torch.empty(B*b, D, K, dtype=x.dtype, device=x.device)\n",
    "        grid = lambda meta: (B, triton.cdiv(D, meta['BLOCK_SIZE_D']), b)\n",
    "        _conv1d_bwd_kernel[grid](dy, dx, dw,\n",
    "                                x, weight,\n",
    "                                *dy.stride(),\n",
    "                                *dx.stride(),\n",
    "                                *x.stride(),\n",
    "                                *weight.stride(),\n",
    "                                *dw.stride(),\n",
    "                                B, D, L, K,\n",
    "                                BLOCK_SIZE_L, triton.next_power_of_2(K),\n",
    "                                # BLOCK_SIZE_D,\n",
    "                                # num_warps=4, num_stages=1\n",
    "                                )\n",
    "        dw = dw.sum(0)\n",
    "        dbias = None\n",
    "        if ctx.bias is not None:\n",
    "            dbias = dy.transpose(-1,-2).reshape(-1, D).sum(0)\n",
    "        return dx, dw, dbias\n",
    "    \n",
    "triton_causal_conv1d2 = _TritonCausalConv1dFunction2.apply"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# v3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# @triton.autotune([triton.Config({\"BLOCK_SIZE_L\":bsl, \"BLOCK_SIZE_D\":bsd}, num_stages=ns, num_warps=nw)\n",
    "#                                  for bsl in [32, 64, 128]\n",
    "#                                  for bsd in [32, 64, 128]\n",
    "#                                  for ns in [1,2,3,4]\n",
    "#                                  for nw in [4,8]], key=['L', 'D'])\n",
    "@triton.jit\n",
    "def _conv1d_fwd_kernel(X, W, Y, BIAS, HAVE_BIAS,\n",
    "                       stride_xb, stride_xd, stride_xl,\n",
    "                       stride_yb, stride_yd, stride_yl,\n",
    "                       stride_wd, stride_wk,\n",
    "                       B, D, L, K, ACT,\n",
    "                       BLOCK_SIZE_L:tl.constexpr=64, BLOCK_SIZE_D:tl.constexpr=128,\n",
    "                       num_stages: tl.constexpr=3,\n",
    "                        ):\n",
    "    off_b = tl.program_id(0)\n",
    "    off_d = tl.program_id(1) * BLOCK_SIZE_D\n",
    "    off_l = tl.program_id(2) * BLOCK_SIZE_L\n",
    "\n",
    "    X += off_b * stride_xb \n",
    "    Y += off_b * stride_yb\n",
    "    # kk = tl.arange(0, BLOCK_SIZE_K)\n",
    "    dd = tl.arange(0, BLOCK_SIZE_D)\n",
    "    ll = tl.arange(0, BLOCK_SIZE_L)\n",
    "    rows = off_d + dd\n",
    "    cols = off_l + ll - K\n",
    "    x_ptrs = X + rows[:, None] * stride_xd + cols[None, :] * stride_xl \n",
    "    \n",
    "    w_ptrs = W + rows * stride_wd\n",
    "    row_mask = rows < D\n",
    "\n",
    "    acc = tl.zeros((BLOCK_SIZE_D, BLOCK_SIZE_L), dtype=tl.float32)\n",
    "    for _ in tl.range(K):\n",
    "        w = tl.load(w_ptrs, mask=row_mask, other=0.).to(tl.float32)\n",
    "        cols += 1\n",
    "        x_ptrs += stride_xl\n",
    "        col_mask = (cols >= 0) & (cols < L)\n",
    "        x = tl.load(x_ptrs, mask=col_mask[None, :] & row_mask[:, None], other=0.).to(tl.float32)\n",
    "        acc += x * w[:, None]\n",
    "        w_ptrs += stride_wk \n",
    "    if HAVE_BIAS:\n",
    "        bias = tl.load(BIAS+rows, mask=row_mask, other=0.).to(tl.float32)\n",
    "        acc += bias[:, None]\n",
    "    if ACT:\n",
    "        acc *= tl.sigmoid(acc)\n",
    "    y_ptrs = Y + rows[:, None] * stride_yd + (off_l + ll)[None, :] * stride_yl\n",
    "    col_mask = off_l + ll < L\n",
    "    tl.store(y_ptrs, acc, mask=col_mask[None, :] & row_mask[:, None])\n",
    "\n",
    "@triton.jit\n",
    "def _conv1d_bwd_dz_kernel(X, W, DY, DZ, BIAS, HAVE_BIAS,\n",
    "                       stride_xb, stride_xd, stride_xl,\n",
    "                       stride_yb, stride_yd, stride_yl,\n",
    "                       stride_wd, stride_wk,\n",
    "                       B, D, L, K, \n",
    "                       BLOCK_SIZE_L:tl.constexpr=64, BLOCK_SIZE_D:tl.constexpr=128,\n",
    "                       num_stages: tl.constexpr=3,\n",
    "                        ):\n",
    "    off_b = tl.program_id(0)\n",
    "    off_d = tl.program_id(1) * BLOCK_SIZE_D\n",
    "    off_l = tl.program_id(2) * BLOCK_SIZE_L\n",
    "\n",
    "    X += off_b * stride_xb \n",
    "    DY += off_b * stride_yb\n",
    "    DZ += off_b * stride_yb\n",
    "\n",
    "    # kk = tl.arange(0, BLOCK_SIZE_K)\n",
    "    dd = tl.arange(0, BLOCK_SIZE_D)\n",
    "    ll = tl.arange(0, BLOCK_SIZE_L)\n",
    "    rows = off_d + dd\n",
    "    cols = off_l + ll - K\n",
    "    x_ptrs = X + rows[:, None] * stride_xd + cols[None, :] * stride_xl \n",
    "    \n",
    "    w_ptrs = W + rows * stride_wd\n",
    "    row_mask = rows < D\n",
    "\n",
    "    acc = tl.zeros((BLOCK_SIZE_D, BLOCK_SIZE_L), dtype=tl.float32)\n",
    "    for _ in tl.range(K):\n",
    "        w = tl.load(w_ptrs, mask=row_mask, other=0.).to(tl.float32)\n",
    "        cols += 1\n",
    "        x_ptrs += stride_xl\n",
    "        col_mask = (cols >= 0) & (cols < L)\n",
    "        x = tl.load(x_ptrs, mask=col_mask[None, :] & row_mask[:, None], other=0.).to(tl.float32)\n",
    "        acc += x * w[:, None]\n",
    "        w_ptrs += stride_wk \n",
    "    if HAVE_BIAS:\n",
    "        bias = tl.load(BIAS+rows, mask=row_mask, other=0.).to(tl.float32)\n",
    "        acc += bias[:, None]\n",
    "\n",
    "    sig_acc = tl.sigmoid(acc)\n",
    "    dy_ptrs = DY + rows[:, None] * stride_yd + (off_l + ll)[None, :] * stride_yl\n",
    "    dz_ptrs = DZ + rows[:, None] * stride_yd + (off_l + ll)[None, :] * stride_yl\n",
    "    col_mask = off_l + ll<L\n",
    "    dy = tl.load(dy_ptrs, mask=col_mask[None, :] & row_mask[:, None], other=0.)\n",
    "    dz = (sig_acc + acc * sig_acc * (1 - sig_acc)) * dy\n",
    "    tl.store(dz_ptrs, dz, mask=col_mask[None, :] & row_mask[:, None])\n",
    "\n",
    "# @triton.autotune([triton.Config({\"BLOCK_SIZE_D\":bsd}, num_stages=ns, num_warps=nw)\n",
    "#                                 #  for bsl in [32, 64, 128]\n",
    "#                                  for bsd in [16, 32, 64, 128]\n",
    "#                                  for ns in [1,2,3,4]\n",
    "#                                  for nw in [4,8]], key=['L', 'D'])\n",
    "@triton.jit\n",
    "def _conv1d_bwd_dwdb_kernel(DY, DX, DW,\n",
    "                        X, W, DB, HAVE_BIAS,\n",
    "                       stride_dyb, stride_dyd, stride_dyl,\n",
    "                       stride_dxb, stride_dxd, stride_dxl,\n",
    "                       stride_xb, stride_xd, stride_xl,\n",
    "                       stride_wd, stride_wk,\n",
    "                       stride_dwb, stride_dwd, stride_dwk,\n",
    "                       B, D, L, K, ACT,\n",
    "                       BLOCK_SIZE_L:tl.constexpr, BLOCK_SIZE_D:tl.constexpr=16,\n",
    "                       num_warps:tl.constexpr=4, num_stages: tl.constexpr=4,\n",
    "                        ):\n",
    "    off_b = tl.program_id(0)\n",
    "    off_d = tl.program_id(1) * BLOCK_SIZE_D\n",
    "    off_l = tl.program_id(2) * BLOCK_SIZE_L\n",
    "    b = tl.cdiv(L, BLOCK_SIZE_L)\n",
    "\n",
    "    X += off_b * stride_xb \n",
    "    DY += off_b * stride_dyb\n",
    "    DX += off_b * stride_dxb\n",
    "    DW += (off_b * b + tl.program_id(2)) * stride_dwb\n",
    "    dd = tl.arange(0, BLOCK_SIZE_D)\n",
    "    ll = tl.arange(0, BLOCK_SIZE_L)\n",
    "    rows = off_d + dd\n",
    "    cols = off_l + ll + K\n",
    "    col_mask_x = off_l + ll < L\n",
    "    x_ptrs = X + rows[:, None] * stride_xd + (off_l + ll)[None, :] * stride_xl \n",
    "    dx_ptrs = DX + rows[:, None] * stride_dxd + cols[None, :] * stride_dxl\n",
    "    dy_ptrs = DY + rows[:, None] * stride_dyd + cols[None, :] * stride_dyl\n",
    "    w_ptrs = W + rows * stride_wd\n",
    "    dw_ptrs = DW + rows * stride_dwd\n",
    "    row_mask = rows < D\n",
    "\n",
    "    x = tl.load(x_ptrs, mask=col_mask_x[None, :] & row_mask[:, None], other=0.).to(tl.float32)\n",
    "    acc_dx = tl.zeros((BLOCK_SIZE_D, BLOCK_SIZE_L), dtype=tl.float32)\n",
    "    for idx in tl.range(K):\n",
    "        w = tl.load(w_ptrs, mask=row_mask, other=0.).to(tl.float32)\n",
    "        cols -= 1\n",
    "\n",
    "        dy_ptrs -= stride_dyl\n",
    "        col_mask = (cols >= 0) & (cols < L)\n",
    "        \n",
    "        dy = tl.load(dy_ptrs, mask=col_mask[None, :] & row_mask[:, None], other=0.).to(tl.float32)\n",
    "        acc_dx += dy * w[:, None]\n",
    "        dw = tl.sum(dy * x, 1)\n",
    "        tl.store(dw_ptrs, dw, mask=row_mask)\n",
    "        w_ptrs += stride_wk\n",
    "        dw_ptrs += stride_dwk\n",
    "        if idx == K-1 and HAVE_BIAS:\n",
    "            DB += (off_b * b + tl.program_id(2)) * stride_dwb // K\n",
    "            tl.store(DB+rows, tl.sum(dy, 1), mask=row_mask)\n",
    "\n",
    "    dx_ptrs = DX + rows[:, None] * stride_dxd + cols[None, :] * stride_dxl\n",
    "    tl.store(dx_ptrs, acc_dx, mask=col_mask_x[None, :] & row_mask[:, None])\n",
    "\n",
    "\n",
    "def causal_conv1d_fwd(x, weight, bias=None, unuse_arg1=None, unuse_arg2=None, unuse_arg3=None, activation=False):\n",
    "    D, K = weight.shape\n",
    "    B, D, L = x.shape\n",
    "    y = torch.empty_like(x)\n",
    "    HAVE_BIAS = bias is not None\n",
    "    grid = lambda meta: (B, triton.cdiv(D, meta['BLOCK_SIZE_D']), triton.cdiv(L,meta['BLOCK_SIZE_L']))\n",
    "    # print(activation is not None, activation)\n",
    "    _conv1d_fwd_kernel[grid](x, weight, y, bias if HAVE_BIAS else weight, HAVE_BIAS,\n",
    "                            *x.stride(),\n",
    "                            *y.stride(),\n",
    "                            *weight.stride(),\n",
    "                            B, D, L, K, activation,\n",
    "                            )\n",
    "    return y\n",
    "    \n",
    "def causal_conv1d_bwd(x, weight, bias, dy, seq_idx=None, unuse_arg1=None, unuse_arg2=None, dx=None, unuse_arg3=None, activation=False):\n",
    "    D, K = weight.shape\n",
    "    B, D, L = x.shape\n",
    "    HAVE_BIAS = bias is not None\n",
    "    if activation:\n",
    "        dz = torch.empty_like(x)\n",
    "        grid = lambda meta: (B, triton.cdiv(D, meta['BLOCK_SIZE_D']), triton.cdiv(L,meta['BLOCK_SIZE_L']))\n",
    "        # print(activation is not None, activation)\n",
    "        _conv1d_bwd_dz_kernel[grid](x, weight, dy, dz, bias if HAVE_BIAS else weight, HAVE_BIAS,\n",
    "                                *x.stride(),\n",
    "                                *dz.stride(),\n",
    "                                *weight.stride(),\n",
    "                                B, D, L, K, \n",
    "                                )\n",
    "    BLOCK_SIZE_L = 128\n",
    "    # a = triton.cdiv(D, BLOCK_SIZE_D)\n",
    "    b = triton.cdiv(L, BLOCK_SIZE_L)\n",
    "    if dx is None:\n",
    "        dx = torch.empty_like(dy)\n",
    "    dw = torch.empty(B*b, D, K, dtype=x.dtype, device=x.device)\n",
    "    db = None\n",
    "    if HAVE_BIAS:\n",
    "        db = torch.empty(B*b, D, dtype=x.dtype, device=x.device)\n",
    "    grid = lambda meta: (B, triton.cdiv(D, meta['BLOCK_SIZE_D']), b)\n",
    "    _conv1d_bwd_dwdb_kernel[grid](dz if activation else dy, dx, dw,\n",
    "                            x, weight, db if HAVE_BIAS else dw, HAVE_BIAS,\n",
    "                            *dy.stride(),\n",
    "                            *dx.stride(),\n",
    "                            *x.stride(),\n",
    "                            *weight.stride(),\n",
    "                            *dw.stride(),\n",
    "                            B, D, L, K, activation,\n",
    "                            BLOCK_SIZE_L, \n",
    "                            # BLOCK_SIZE_D,\n",
    "                            # num_warps=4, num_stages=1\n",
    "                            )\n",
    "    dw = dw.sum(0)\n",
    "    if HAVE_BIAS:\n",
    "        db = db.sum(0)\n",
    "    return dx, dw, db, None\n",
    "\n",
    "class _TritonCausalConv1dFunction(torch.autograd.Function):\n",
    "    @staticmethod\n",
    "    def forward(ctx, x, weight, bias=None, activation=None):\n",
    "        assert activation in [None, 'silu', 'swish']\n",
    "        # # print(weight.shape, x.shape)\n",
    "        # D, K = weight.shape\n",
    "        # B, D, L = x.shape\n",
    "        # y = torch.empty_like(x)\n",
    "        # HAVE_BIAS = bias is not None\n",
    "        # grid = lambda meta: (B, triton.cdiv(D, meta['BLOCK_SIZE_D']), triton.cdiv(L,meta['BLOCK_SIZE_L']))\n",
    "        # # print(activation is not None, activation)\n",
    "        # _conv1d_fwd_kernel[grid](x, weight, y, bias if HAVE_BIAS else weight, HAVE_BIAS,\n",
    "        #                         *x.stride(),\n",
    "        #                         *y.stride(),\n",
    "        #                         *weight.stride(),\n",
    "        #                         B, D, L, K, activation is not None,\n",
    "        #                         )\n",
    "        ctx.activation = activation in ['silu', 'swish']\n",
    "        y = causal_conv1d_fwd(x, weight, bias, None, None, None, ctx.activation)\n",
    "        ctx.save_for_backward(x, weight)\n",
    "        ctx.bias = bias\n",
    "        return y\n",
    "    \n",
    "    @staticmethod\n",
    "    def backward(ctx, dy):\n",
    "        x, weight = ctx.saved_tensors\n",
    "        # D, K = weight.shape\n",
    "        # B, D, L = x.shape\n",
    "        # HAVE_BIAS = ctx.bias is not None\n",
    "        # if ctx.activation is not None:\n",
    "        #     dz = torch.empty_like(x)\n",
    "        #     grid = lambda meta: (B, triton.cdiv(D, meta['BLOCK_SIZE_D']), triton.cdiv(L,meta['BLOCK_SIZE_L']))\n",
    "        #     # print(activation is not None, activation)\n",
    "        #     _conv1d_bwd_dz_kernel[grid](x, weight, dy, dz, ctx.bias if HAVE_BIAS else weight, HAVE_BIAS,\n",
    "        #                             *x.stride(),\n",
    "        #                             *dz.stride(),\n",
    "        #                             *weight.stride(),\n",
    "        #                             B, D, L, K, \n",
    "        #                             )\n",
    "        # BLOCK_SIZE_L = 128\n",
    "        # a = triton.cdiv(D, BLOCK_SIZE_D)\n",
    "        # b = triton.cdiv(L, BLOCK_SIZE_L)\n",
    "        # dx = torch.empty_like(dy)\n",
    "        # dw = torch.empty(B*b, D, K, dtype=x.dtype, device=x.device)\n",
    "        # db = None\n",
    "        # if ctx.bias is not None:\n",
    "        #     db = torch.empty(B*b, D, dtype=x.dtype, device=x.device)\n",
    "        # grid = lambda meta: (B, triton.cdiv(D, meta['BLOCK_SIZE_D']), b)\n",
    "        # _conv1d_bwd_dwdb_kernel[grid](dy if ctx.activation is None else dz, dx, dw,\n",
    "        #                         x, weight, db if HAVE_BIAS else dw, HAVE_BIAS,\n",
    "        #                         *dy.stride(),\n",
    "        #                         *dx.stride(),\n",
    "        #                         *x.stride(),\n",
    "        #                         *weight.stride(),\n",
    "        #                         *dw.stride(),\n",
    "        #                         B, D, L, K, ctx.activation is not None,\n",
    "        #                         BLOCK_SIZE_L, \n",
    "        #                         # BLOCK_SIZE_D,\n",
    "        #                         # num_warps=4, num_stages=1\n",
    "        #                         )\n",
    "        # dw = dw.sum(0)\n",
    "        # if ctx.bias is not None:\n",
    "        #     db = db.sum(0)\n",
    "        dx, dw, db, *_ = causal_conv1d_bwd(x, weight, ctx.bias, dy, None, None, None, None, None, ctx.activation)\n",
    "        return dx, dw, db, None\n",
    "    \n",
    "triton_causal_conv1d = _TritonCausalConv1dFunction.apply"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "dtype = torch.bfloat16\n",
    "bs = 64\n",
    "seq_len = 1024\n",
    "d = 1024\n",
    "k = 4\n",
    "x1 = torch.randn(bs, seq_len, d, dtype=dtype).cuda()\n",
    "x1.requires_grad_(True)\n",
    "x2 = deepcopy(x1)\n",
    "# x11,_ = x1.chunk(2, -1)\n",
    "# x22,_ = x2.chunk(2, -1)\n",
    "# d = d//2\n",
    "conv1 = torch.nn.Conv1d(d,d,kernel_size=k, groups=d,padding=k-1,bias=True).to(dtype).cuda()\n",
    "conv2 = deepcopy(conv1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[ 0.0039,  0.2500, -0.0300,  ...,  0.1943, -0.1299,  0.1562],\n",
      "        [ 0.1060,  0.2354,  0.0835,  ...,  0.0923, -0.1279, -0.1069],\n",
      "        [ 0.0825,  0.2500, -0.0366,  ..., -0.0879, -0.0493,  0.0693],\n",
      "        ...,\n",
      "        [ 0.0518,  0.0187,  0.0981,  ..., -0.2432, -0.0991,  0.1006],\n",
      "        [ 0.0752,  0.0034,  0.1719,  ..., -0.1211, -0.1963,  0.0215],\n",
      "        [-0.0270,  0.0006,  0.0229,  ...,  0.2197,  0.0364,  0.0148]],\n",
      "       device='cuda:0', dtype=torch.bfloat16)\n",
      "tensor([[ 0.0040,  0.2520, -0.0300,  ...,  0.1953, -0.1299,  0.1562],\n",
      "        [ 0.1060,  0.2354,  0.0830,  ...,  0.0913, -0.1279, -0.1069],\n",
      "        [ 0.0830,  0.2500, -0.0366,  ..., -0.0874, -0.0493,  0.0693],\n",
      "        ...,\n",
      "        [ 0.0518,  0.0187,  0.0986,  ..., -0.2422, -0.0991,  0.1006],\n",
      "        [ 0.0752,  0.0034,  0.1719,  ..., -0.1206, -0.1963,  0.0214],\n",
      "        [-0.0269,  0.0006,  0.0229,  ...,  0.2197,  0.0361,  0.0148]],\n",
      "       device='cuda:0', dtype=torch.bfloat16)\n",
      "tensor([[ 1808.,   916.,  4608., -1528.],\n",
      "        [ 4192.,  3888.,   412.,   420.],\n",
      "        [-2752., -2288.,  1608.,  4832.],\n",
      "        ...,\n",
      "        [ 5088., -5120., -3840.,  5280.],\n",
      "        [-3712.,  -410., -6528.,  1096.],\n",
      "        [  -83.,  5248., -4640.,  3520.]], device='cuda:0',\n",
      "       dtype=torch.bfloat16)\n",
      "tensor([[ 1808.,   916.,  4608., -1528.],\n",
      "        [ 4192.,  3888.,   412.,   420.],\n",
      "        [-2752., -2288.,  1608.,  4832.],\n",
      "        ...,\n",
      "        [ 5088., -5120., -3840.,  5280.],\n",
      "        [-3712.,  -410., -6528.,  1096.],\n",
      "        [  -83.,  5248., -4640.,  3520.]], device='cuda:0',\n",
      "       dtype=torch.bfloat16)\n",
      "tensor([10176., 17536., 22656.,  ..., 20864., 18304.,  9728.], device='cuda:0',\n",
      "       dtype=torch.bfloat16)\n",
      "tensor([10176., 17536., 22656.,  ..., 20864., 18304.,  9728.], device='cuda:0',\n",
      "       dtype=torch.bfloat16)\n",
      "0.1990566998720169\n",
      "0.20597851276397705\n",
      "0.748494029045105\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.2824690341949463\n"
     ]
    }
   ],
   "source": [
    "torch.cuda.empty_cache()\n",
    "y1 = causal_conv1d_fn(x1.transpose(-1,-2), conv1.weight.squeeze(), bias=conv1.bias, activation='silu')\n",
    "dy = torch.rand_like(y1)\n",
    "y1.backward(dy, retain_graph=True)\n",
    "y2 = triton_causal_conv1d(x2.transpose(-1,-2), conv2.weight.squeeze(), conv2.bias, 'silu')\n",
    "y2.backward(dy, retain_graph=True)\n",
    "print(x1.grad[0])\n",
    "print(x2.grad[0])\n",
    "print(conv1.weight.grad.squeeze())\n",
    "print(conv2.weight.grad.squeeze())\n",
    "print(conv1.bias.grad)\n",
    "print(conv2.bias.grad)\n",
    "print(triton.testing.do_bench(lambda: causal_conv1d_fn(x1.transpose(-1, -2), conv1.weight.squeeze(), activation='silu')))\n",
    "print(triton.testing.do_bench(lambda: triton_causal_conv1d(x2.transpose(-1, -2), conv2.weight.squeeze(), None, 'silu')))\n",
    "print(triton.testing.do_bench(lambda: y1.backward(dy, retain_graph=True)))\n",
    "print(triton.testing.do_bench(lambda: y2.backward(dy, retain_graph=True)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 98,
   "metadata": {},
   "outputs": [],
   "source": [
    "dx = torch.randn(bs, seq_len, d*2, dtype=dtype).cuda()\n",
    "dx1, dx2 = dx.chunk(2, -1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 109,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[-0.4141,  0.0293, -0.3711,  ..., -0.1270, -0.1914, -0.0236],\n",
       "        [ 0.0840, -0.0123, -0.0309,  ...,  0.0427,  0.0437, -0.0067],\n",
       "        [ 0.1279,  0.0454,  0.1748,  ...,  0.1050, -0.1094,  0.0420],\n",
       "        ...,\n",
       "        [-0.0559,  0.2637, -0.0201,  ..., -0.1650,  0.1855, -0.1709],\n",
       "        [ 0.0601,  0.0811,  0.2295,  ...,  0.0771,  0.0713,  0.0425],\n",
       "        [-0.3242, -0.1797, -0.1895,  ..., -0.0178,  0.0056,  0.0010]],\n",
       "       device='cuda:0', dtype=torch.bfloat16)"
      ]
     },
     "execution_count": 109,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "causal_conv1d_bwd(x2.transpose(-1, -2), conv2.weight.squeeze(), conv2.bias, dy, \n",
    "                  None, None, None, dx2.transpose(-1, -2), None, True)[0][0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "metadata": {},
   "outputs": [],
   "source": [
    "import causal_conv1d_cuda"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 108,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[-0.4141,  0.0297, -0.3711,  ..., -0.1279, -0.1914, -0.0236],\n",
       "        [ 0.0840, -0.0126, -0.0308,  ...,  0.0427,  0.0437, -0.0067],\n",
       "        [ 0.1279,  0.0457,  0.1748,  ...,  0.1050, -0.1094,  0.0422],\n",
       "        ...,\n",
       "        [-0.0559,  0.2637, -0.0198,  ..., -0.1650,  0.1865, -0.1709],\n",
       "        [ 0.0601,  0.0811,  0.2295,  ...,  0.0771,  0.0713,  0.0425],\n",
       "        [-0.3242, -0.1797, -0.1895,  ..., -0.0178,  0.0057,  0.0010]],\n",
       "       device='cuda:0', dtype=torch.bfloat16)"
      ]
     },
     "execution_count": 108,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "causal_conv1d_cuda.causal_conv1d_bwd(x1.transpose(-1, -2), conv1.weight.squeeze(), conv1.bias, dy, \n",
    "                                     None, None, None, dx1.transpose(-1, -2), None, True)[0][0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[-0.2197, -0.1963,  0.3340,  ...,  0.1040, -0.1162, -0.2578],\n",
       "        [-0.0449, -0.0786, -0.2715,  ..., -0.2158, -0.1523,  0.4648],\n",
       "        [-0.0615, -0.0713,  0.2324,  ..., -0.0640, -0.1328, -0.2773],\n",
       "        ...,\n",
       "        [ 0.1465, -0.2539,  0.6953,  ...,  0.1377,  0.0747, -0.2227],\n",
       "        [ 0.6680,  0.0452, -0.0840,  ...,  0.3086, -0.1147,  0.5898],\n",
       "        [ 0.1631, -0.0262,  0.0811,  ...,  0.9531, -0.2188, -0.2656]],\n",
       "       device='cuda:0', dtype=torch.bfloat16, grad_fn=<SelectBackward0>)"
      ]
     },
     "execution_count": 87,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y1[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# forward"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkQAAAGxCAYAAACDV6ltAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/GU6VOAAAACXBIWXMAAA9hAAAPYQGoP6dpAABYbUlEQVR4nO3dd3xUVf7/8dekTepMCiQBCYiC9F4Dtl1RVHR11f2hsoqr4qKgoqsiFkT3u6LYC5ZddwULtl07oLI0V4g0QXoEBEEhCSWZSUifOb8/LoyEGkKSO8m8n4/HPJhz752Zz1wnM2/vPfcchzHGICIiIhLCwuwuQERERMRuCkQiIiIS8hSIREREJOQpEImIiEjIUyASERGRkKdAJCIiIiFPgUhERERCngKRiIiIhLwIuwtoCPx+P9u3bychIQGHw2F3OSIiIlINxhgKCwtp3rw5YWFHPwakQFQN27dvJyMjw+4yREREpAa2bdtGixYtjrqNAlE1JCQkANYOdblcNlcjIiIi1eH1esnIyAj8jh+NAlE17D9N5nK5FIhEREQamOp0d1GnahEREQl5CkQiIiIS8hSIREREJOSpD1Et8vl8VFRU2F1GSIqMjCQ8PNzuMkREpIFSIKoFxhhycnIoKCiwu5SQlpiYSHp6usaKEhGR46ZAVAv2h6HU1FRiY2P1g1zPjDEUFxeTl5cHQLNmzWyuSEREGhoFohPk8/kCYSglJcXuckJWTEwMAHl5eaSmpur0mYiIHBd1qj5B+/sMxcbG2lyJ7P9voH5cIiJyvBSIaolOk9lP/w1ERKSmFIhEREQk5CkQSbVNmDCB7t27212GiIhIrVMgClEOh+OotwkTJhzymLvuuovZs2cH2tdddx2XXnpp/RUtIiJSR3SVWYjasWNH4P57773H+PHjyc7ODiyLj48P3DfG4PP5iI+Pr7JcRESkVuTMgfAYaJppWwk6QhSi0tPTAze3243D4Qi0169fT0JCAjNnzqRXr144nU6++eabKqfMJkyYwNSpU/nkk08CR5XmzZsHwKpVq/jtb39LTEwMKSkp3HTTTRQVFQVee/+RpSeffJJmzZqRkpLCqFGjdHWYiEio8VfAinEwZxAsuBLK820rRUeI6oAxUFxsz2vHxkJtXWx177338uSTT3LKKaeQlJQUCDxgnT5bt24dXq+X119/HYDk5GT27t3L4MGDyczMZMmSJeTl5XHjjTcyevRopkyZEnj83LlzadasGXPnzmXjxo0MHTqU7t27M2LEiNopXkREglvhJlh4NexebLWbDYawKNvKUSCqA8XFYNeZpaIiiIurned65JFHOPfccw+7Lj4+npiYGMrKykhPTw8snzp1KqWlpbzxxhvE7SvkxRdf5OKLL+bxxx8nLS0NgKSkJF588UXCw8Np3749Q4YMYfbs2QpEIiKhYPNbsOQWqCyEyETo9w9oeYWtJemUmRxR7969j/sx69ato1u3boEwBDBw4ED8fn+VPkqdOnWqMpp0s2bNAlNviIhII1XhhYXXQNY1VhhqegZc+L3tYQh0hKhOxMZaR2rseu3aEldbh5oOIzIyskrb4XDg9/vr7PVERMRmuxbDwqug6EdwhEPnh6DTfRAWHFMtKRDVAYej9k5bBbOoqCh8Pl+VZR06dGDKlCns3bs3EKgWLFhAWFgY7dq1s6NMERGxk/HD2kmw8kEwlRDXCgZMg6YD7K6sCp0ykxo7+eSTWblyJdnZ2ezatYuKigqGDRtGdHQ0w4cPZ/Xq1cydO5dbb72Va665JtB/SEREQkTxLzDnXPh+nBWGWg6FC1YEXRgCBSI5ASNGjKBdu3b07t2bpk2bsmDBAmJjY/nyyy/Zs2cPffr04YorruCcc87hxRdftLtcERGpTz9/CjO7Qe4ciIiDfv+Cge9AVKLdlR2Wwxhj7C4i2Hm9XtxuNx6PB5fLVWVdaWkpmzdvpnXr1kRHR9tUoYD+W4iIBIXKElh+F2x4yWon9bSCkOu0ei/laL/fB1MfIhEREakdBathwVXgWW212/8Fuv0Nwp321lUNCkQiIiJyYoyBDS/D8r+ArxSi06D/VGg+2O7Kqk2BSERERGqudBcsugF++dRqN7sAMqdAdKqtZR0vBSIRERGpmZw51iCLJdutaTe6T4J2t9XeHFL1SIFIREREjo+/AlaOh7WPAwZc7a2O00nd7a6sxhSIREREpPoOnpT11BHQ6xnr0voGTIFIREREqicIJ2WtLQpEIiIicnQVXlgyCra8ZbWbngED3oK4lvbWVYs0UrVU24QJE+jevbvdZYiISH3atRhm9rDCkCMMujwM58xtVGEIFIhClsPhOOptwoQJhzzmrrvuYvbs2Ud8zi1btlR5joSEBDp16sSoUaPYsGFDHb4bERGpdcYPax6DWQOtGerjWsGgr6HL+KCZob426ZRZiNqxY0fg/nvvvcf48ePJzs4OLIuPjw/cN8bg8/mIj4+vsvxI/vvf/9KpUyeKi4tZtWoVzz33HN26deOzzz7jnHPOqd03IiIita/4F8i61pqHDKxJWfu+ErTzkNUGHSEKUenp6YGb2+3G4XAE2uvXrychIYGZM2fSq1cvnE4n33zzTbVPmaWkpJCens4pp5zCJZdcwn//+1/69evHDTfcgM/nA2DTpk1ccsklpKWlER8fT58+ffjvf/9b5Xny8/O59tprSUpKIjY2lgsuuEBHmkRE6loDm5S1tigQyRHde++9PPbYY6xbt46uXbvW+HnCwsK4/fbb+emnn1i2bBkARUVFXHjhhcyePZvly5dz/vnnc/HFF7N169bA46677jqWLl3Kp59+SlZWFsYYLrzwQioqKk74vYmIyEF8pbBkNHx9CZTttiZlPf87OPVPDXKgxeOlU2Z1wBhDcUWxLa8dGxmLo5Y+uI888gjnnnturTxX+/btAaufUd++fenWrRvdunULrP/rX//KRx99xKeffsro0aPZsGEDn376KQsWLGDAgAEAvP3222RkZPDxxx/zhz/8oVbqEhERwLMeFgyFgpVWuwFNylpbFIjqQHFFMfETj93Xpi4UjSsiLqp2Bsfq3bt3rTwPWCERCIS1oqIiJkyYwPTp09mxYweVlZWUlJQEjhCtW7eOiIgI+vXrF3iOlJQU2rVrx7p162qtLhGRkGYMbJ5qXVLvKwZnU8h8A5qfb3dl9U6BSI4oLq72Rh3dH2Jat24NWFeszZo1iyeffJI2bdoQExPDFVdcQXl5ea29poiIHEVFoTXI4v6xhdLOgQFvQkwze+uyiQJRHYiNjKVoXJFtrx1s/H4/zz//PK1bt6ZHjx4ALFiwgOuuu47f//73gHXEaMuWLYHHdOjQgcrKShYtWhQ4ZbZ7926ys7Pp2LFjvb8HEZFGZc9y6xRZ4QZwhEPXR6DD2EZ5OX11KRDVAYfDUWunrRqi3bt3k5OTQ3FxMatXr+bZZ59l8eLFTJ8+nfBw64+tbdu2fPjhh1x88cU4HA4efPBB/H5/4Dnatm3LJZdcwogRI3j11VdJSEjg3nvv5aSTTuKSSy6x662JiDRsxsAPL8Dyu8FfDrEZMGAapJ5ud2W2UyCSWjdo0CAAYmNjadWqFb/5zW/4+9//Tps2bQLbPP3001x//fUMGDCAJk2aMHbsWLxeb5Xnef3117n99tu56KKLKC8v58wzz2TGjBlERkbW6/sREWkUynbDt9fDL59a7RaXQr9/gjPZ1rKChcPs7+0qR+T1enG73Xg8HlwuV5V1paWlbN68mdatWxMdHW1ThQL6byEickR538DCq6D4ZwiLgh5PwWmjGv3l9Ef7/T6YjhCJiIg0Vn4frJ0Iqx6ypuJIaAsD34PkHnZXFnSCZmDGxx57DIfDwZgxYwLLSktLGTVqFCkpKcTHx3P55ZeTm5tb5XFbt25lyJAhxMbGkpqayt13301lZWWVbebNm0fPnj1xOp20adOGKVOm1MM7EhERsVHJDph7Hqx80ApDJ18D5y9TGDqCoAhES5Ys4dVXXz1kNOQ77riDzz77jA8++ID58+ezfft2LrvsssB6n8/HkCFDKC8vZ+HChUydOpUpU6Ywfvz4wDabN29myJAh/OY3v2HFihWMGTOGG2+8kS+//LLe3p+IiEi92v4FzNg3/UZ4LPSfAgPegMgEuysLXsZmhYWFpm3btmbWrFnmrLPOMrfffrsxxpiCggITGRlpPvjgg8C269atM4DJysoyxhgzY8YMExYWZnJycgLbvPzyy8blcpmysjJjjDH33HOP6dSpU5XXHDp0qBk8eHC1a/R4PAYwHo/nkHUlJSVm7dq1pqSkpNrPJ3VD/y1EJOT5yo357h5j3sa6Te9qjGe93VXZ5mi/3wez/QjRqFGjGDJkSODKpP2WLVtGRUVFleXt27enZcuWZGVlAZCVlUWXLl1IS0sLbDN48GC8Xi9r1qwJbHPwcw8ePDjwHIdTVlaG1+utchMREQlqRZth1hmwbpLVbjsKBi8CVzt762ogbO1U/e677/Ldd9+xZMmSQ9bl5OQQFRVFYmJileVpaWnk5OQEtjkwDO1fv3/d0bbxer2UlJQQExNzyGtPnDiRhx9+uMbvS0REpF5t/TcsuhEqPBCZCP3/CRmXHfNh8ivbjhBt27aN22+/nbfffjvoLpEeN24cHo8ncNu2bZvdJYmIiByqsgQW3wzf/MEKQ00y4YLlCkM1YFsgWrZsGXl5efTs2ZOIiAgiIiKYP38+zz//PBEREaSlpVFeXk5BQUGVx+Xm5pKeng5Aenr6IVed7W8faxuXy3XYo0MATqcTl8tV5SYiIhJUPOvgq36w8RWr3fFeGDQf4k+2tayGyrZAdM4557Bq1SpWrFgRuPXu3Zthw4YF7kdGRjJ79uzAY7Kzs9m6dSuZmZkAZGZmsmrVKvLy8gLbzJo1C5fLFZjvKjMzs8pz7N9m/3OIiIg0KMbAptfhi95QsAqiU+E3X0L3iRCmkfxryrY+RAkJCXTu3LnKsri4OFJSUgLLb7jhBu68806Sk5NxuVzceuutZGZm0r9/fwDOO+88OnbsyDXXXMOkSZPIycnhgQceYNSoUTidTgBGjhzJiy++yD333MP111/PnDlzeP/995k+fXr9vuEQdt1111FQUMDHH39sdykiIg1bRSEsHgk/TbPa6YMg802ISbe3rkbA9qvMjuaZZ57hoosu4vLLL+fMM88kPT2dDz/8MLA+PDyczz//nPDwcDIzM/njH//ItddeyyOPPBLYpnXr1kyfPp1Zs2bRrVs3nnrqKV577TUGDx5sx1sKKmeffXaVgTBFRCSI7fkOZva0wpAjHLo9ah0ZUhiqFUE1dce8efOqtKOjo5k8eTKTJ08+4mNatWrFjBkzjvq8Z599NsuXL6+NEuUg5eXlREVF2V2GiEjjZQxkPw8r7gZ/BcS2hIHvQNMBdlfWqAT1ESKpO9dddx3z58/nueeew+Fw4HA42LJlC/Pnz6dv3744nU6aNWvGvffeW2UqlLPPPpvRo0czZswYmjRpEjjStmbNGi666CJcLhcJCQmcccYZbNq0qcprPvnkkzRr1oyUlBRGjRpFRUVFvb5nEZEGp2w3fH0JfDfGCkMtLrWuIlMYqnVBdYRI6s9zzz3HDz/8QOfOnQOnGH0+HxdeeCHXXXcdb7zxBuvXr2fEiBFER0czYcKEwGOnTp3KzTffzIIFCwD45ZdfOPPMMzn77LOZM2cOLpeLBQsWVAlSc+fOpVmzZsydO5eNGzcydOhQunfvzogRI+r1fYuINBh5/4OFV/86Q33Pp6HtLY1+hnq7KBDVBWPAV2zPa4fHVuuPxe12ExUVRWxsbGCIgvvvv5+MjAxefPFFHA4H7du3Z/v27YwdO5bx48cTFmYdUGzbti2TJk0KPNd9992H2+3m3XffJTLSusLhtNNOq/J6SUlJvPjii4SHh9O+fXuGDBnC7NmzFYhERA7m98GaR2H1hH0z1J8Gp78HSd3trqxRUyCqC75ieD/entf+f0UQEVejh65bt47MzEwcBwSqgQMHUlRUxM8//0zLli0B6NWrV5XHrVixgjPOOCMQhg6nU6dOhIeHB9rNmjVj1apVNapTRKTRKv4FFv4R8uZZ7dbXQu/JEGnTb0oIUSCS4xYXVzVwHWmAywMdHJYcDgd+v79W6xIRadB++Ry+vc7qNxQRB71fglOutbuqkKFAVBfCY60jNXa9djVFRUXh8/kC7Q4dOvCf//wHY0zgKNGCBQtISEigRYsWR3yerl27MnXqVCoqKo56lEhERA7DVwYr7oXsZ612Ug8Y+C64Tjvqw6R26SqzuuBwWOnejttxdLY7+eSTWbRoEVu2bGHXrl3ccsstbNu2jVtvvZX169fzySef8NBDD3HnnXcG+g8dzujRo/F6vVx55ZUsXbqUDRs28Oabb5KdnV0be1NEpPHyboCvBvwahtqNgfOyFIZsoEAUwu666y7Cw8Pp2LEjTZs2paKighkzZrB48WK6devGyJEjueGGG3jggQeO+jwpKSnMmTOHoqIizjrrLHr16sU//vEPHS0SETmazW/CFz0h/ztwpsBZn0GvZyDcaXdlIclhjDF2FxHsvF4vbrcbj8dzyESvpaWlbN68mdatWxMdHW1ThQL6byEiDURFISwZBVvetNqpZ8GAtyH2JHvraoSO9vt9MPUhEhERqS97voMFV0LhBnCEQecJ0Ok+CAs/5kOlbikQiYiI1DVjIPs5WHHPvuk3MmDANEg93e7KZB8FIhERkbpUuhO+/RNsn261W1wK/f4JzmRby5KqFIhERETqSu5cWDgMSnZAmHPf9Bs3a/qNIKRAJCIiUtv8lbDqYVjzN8CAq4M1tlBSV7srkyNQIKoluljPfvpvICJBYe9Wa1LWndYE2Jx6A/R6rsbTKkn9UCA6QfvH2ikuLq7WFBZSd4qLrQl1Nf6RiNhm24fw7Q1QUQCRLuj7d2g11O6qpBoUiE5QeHg4iYmJ5OXlARAbG1tlclSpe8YYiouLycvLIzExscoksiIi9aKyBJb/BTa8bLVT+sLAdyD+FHvrkmpTIKoF6enpAIFQJPZITEwM/LcQEak3nrXW2EIFq6x2h3ug2/9BmI5WNyQKRLXA4XDQrFkzUlNTqaiosLuckBQZGakjQyJSv4yBTf+EZbeBrwSiUyHzTWh2nt2VSQ0oENWi8PBw/SiLiISCcg8svgm2vm+108+DzDcgJs3euqTGFIhERESOx65vYcFVsHcLOCKg29+gw13WVBzSYCkQiYiIVIfxw7on4PsHwFRC3MnW2EJN+tldmdQCBSIREZFjKcmBrGshZ5bVbjkU+r4KUW5765Jao0AkIiJyNNu/hG+vhdI8CI+B3i/AKddr+o1GRoFIRETkcHzlsPIB6zQZQGIX6xSZu6O9dUmdUCASERE5mPcHa/qNPcusdttboMeTEKEZCRorBSIREZH9jIEfp8CyW6FyL0QlQb9/Qsbv7a5M6pgCkYiICEB5Piwe+evYQqlnw4A3IbaFrWVJ/VAgEhERyfsfLPwjFG+1xhbq+lfocDeEabDdUKFAJCIioctfCasfgTV/s8YZij8VBkyDJn3trkzqmQKRiIiEpqLNsHAY7Mqy2q2HW5fURybYW5fYQoFIRERCz5ZpsORmqPBCpAv6vAonX2l3VWIjBSIREQkdFV5YMhq2vGm1mw6EzLcg/mRbyxL7KRCJiEho2LXIGluo6EdrItbO46HT/RCmn0JRIBIRkcbO74O1j8Gqh8D4IK4VDHjbOjokso8CkYiINF57t0HWHyHva6vd6kro8zJEJdpalgQfBSIREWmctv4bFo2AigKIiIfek6H1NZqUVQ5LgUhERBqXyr2w7HbY9E+rndwHBk6DhDb21iVBTYFIREQajz3LYMHVUPgD4IBO46DLBAiLtLsyCXIKRCIi0vAZP6x/Gr6/D/wVEHMSDHgL0s62uzJpIBSIRESkYSveDt8Oh5z/Wu2My6DvP8CZbG9d0qAoEImISMP186ew6Hoo2w3hsdDrWTj1RnWcluOmQCQiIg1PZQksvws2vGS1k7rDgHfA3d7WsqThUiASEZGGJX8lLLwKPGutdvu/QLe/QbjT3rqkQVMgEhGRhsEY+OEFWH4P+MsgOg0y34Bm59ldmTQCCkQiIhL8SvPg2z/B9hlWu/kQ6P8viE61ty5pNBSIREQkuG3/0rqKrDQXwpzQ8yloe4s6TkutUiASEZHg5CuHlffDuiettruzNeJ0Yhd765JGSYFIRESCT+FGWHAV7FlqtdveAj2ehIgYe+uSRkuBSEREgsvmt2DJzVBZBFFJ0O9fkHGp3VVJI6dAJCIiwaGiEJaMgi1vWu3UMyHzLYjLsLcuCQkKRCIiYr89y+CbK6FoIzjCoPME6HQfhIXbXZmECAUiERGxj/HD+mfg+3HWpKyxGTBgGqSebndlEmIUiERExB4lufDtdbDjC6utSVnFRgpEIiJS/3Z8BVnXWmMLhUdDz2ehzU0aW0hso0AkIiL1x1cOKx+AdU9YbXdnGPguJHayty4JeQpEIiJSPwo37RtbaInV1thCEkQUiEREpO5tfnvf2EKF+8YW+idk/N7uqkQCFIhERKTuVBTC0tGw+Q2r3fQMGPAWxLW0ty6RgygQiYhI3dizzDpFVrhh39hC46HT/RCmnx4JPvpUiohI7TJ+WP8sfH/vAWMLvQ2pZ9hdmcgRKRCJiEjtOXhsoRa/h36vaWwhCXoKRCIiUjt2zIKsaw4YW+gZaPNnjS0kDYICkYiInBh/BXz/AKybZLXdnfaNLdTZ3rpEjoMCkYiI1FzhJlh4NexebLXbjISeT0FErL11iRynMDtf/OWXX6Zr1664XC5cLheZmZnMnDkzsL60tJRRo0aRkpJCfHw8l19+Obm5uVWeY+vWrQwZMoTY2FhSU1O5++67qaysrLLNvHnz6NmzJ06nkzZt2jBlypT6eHsiIo3blmkws4cVhiIT4Yz/QN+XFYakQbI1ELVo0YLHHnuMZcuWsXTpUn77299yySWXsGbNGgDuuOMOPvvsMz744APmz5/P9u3bueyyywKP9/l8DBkyhPLychYuXMjUqVOZMmUK48ePD2yzefNmhgwZwm9+8xtWrFjBmDFjuPHGG/nyyy/r/f2KiDQKFUWQdR0sHGYNtNj0dLjwe2tyVpEGymGMMXYXcaDk5GSeeOIJrrjiCpo2bcq0adO44oorAFi/fj0dOnQgKyuL/v37M3PmTC666CK2b99OWloaAK+88gpjx45l586dREVFMXbsWKZPn87q1asDr3HllVdSUFDAF198Ua2avF4vbrcbj8eDy+Wq/TctItJQ7PkOFlz569hCnR6Ezg9obCEJSsfz+23rEaID+Xw+3n33Xfbu3UtmZibLli2joqKCQYMGBbZp3749LVu2JCsrC4CsrCy6dOkSCEMAgwcPxuv1Bo4yZWVlVXmO/dvsf47DKSsrw+v1VrmJiIQ044d1T8FX/a0wFNsCzpkLXScoDEmjYHsgWrVqFfHx8TidTkaOHMlHH31Ex44dycnJISoqisTExCrbp6WlkZOTA0BOTk6VMLR//f51R9vG6/VSUlJy2JomTpyI2+0O3DIyMmrjrYqINEwlO2Du+bD8LuuKshaXwgUrIPVMuysTqTW2B6J27dqxYsUKFi1axM0338zw4cNZu3atrTWNGzcOj8cTuG3bts3WekREbPPzpzCjC+TMgvAY6PMKnPEhOFPsrkykVtl+nDMqKoo2bdoA0KtXL5YsWcJzzz3H0KFDKS8vp6CgoMpRotzcXNLT0wFIT09n8eLFVZ5v/1VoB25z8JVpubm5uFwuYmJiDluT0+nE6XTWyvsTEWmQKoutI0IbXrbaid1g4Dvg7mBvXSJ1xPYjRAfz+/2UlZXRq1cvIiMjmT17dmBddnY2W7duJTMzE4DMzExWrVpFXl5eYJtZs2bhcrno2LFjYJsDn2P/NvufQ0REDpL/PXzR+9cw1P5OGLxIYUgaNVuPEI0bN44LLriAli1bUlhYyLRp05g3bx5ffvklbrebG264gTvvvJPk5GRcLhe33normZmZ9O/fH4DzzjuPjh07cs011zBp0iRycnJ44IEHGDVqVOAIz8iRI3nxxRe55557uP7665kzZw7vv/8+06dPt/Oti4gEH+OH7Odgxb3gL4fodMicCs3Os7sykTpnayDKy8vj2muvZceOHbjdbrp27cqXX37JueeeC8AzzzxDWFgYl19+OWVlZQwePJiXXnop8Pjw8HA+//xzbr75ZjIzM4mLi2P48OE88sgjgW1at27N9OnTueOOO3juuedo0aIFr732GoMHD6739ysiErRKcvZNyrpvjLaTLoZ+/4TopraWJVJfgm4comCkcYhEpFH75XP49noo27lvUtanrSk4NCmrNHDH8/tte6dqERGxSWUJLL8bNky22old93Wc7mhvXSI2UCASEQlF+SutSVk91iC2tBsD3SdaR4hEQpACkYhIKDEGfngBlt8D/jKIToP+U6D5+XZXJmIrBSIRkVBRkgvf/gl2zLTazYdA/39BdKq9dYkEAQUiEZFQ8MsMWPQnKM2zTov1eBLa3qKO0yL7KBCJiDRmvlLr9NgPL1jtxC4w4B1I7GRvXSJBRoFIRKSxKlgNC64Cz2qrfdpt0ONxdZwWOQwFIhGRxsYY+GHyvtnpy6w+Qv2nQPML7K5MJGgpEImINCaledYgi9v3TU/U7ALo/zrEpNlbl0iQUyASEWkstn9hTb9RmgthTujxBJw2Wh2nRapBgUhEpKHzlVoTsmY/Z7XdnawRpxO72FuXSAOiQCQi0pAVrLFGnC5YabVPGw3dJ0FEjL11iTQwCkQiIg2RMbDhJavjtK8UnE2tvkInDbG7MpEGSYFIRKShKd25r+P051a72fn7Ok6n21uXSAOmQCQi0pDkzIasa6BkB4RFQffHod1t4AizuzKRBk2BSESkIfBXwMrxsPZxwICrg9VxOqmb3ZWJNAoKRCIiwa7oR2vE6d2LrXabm6DnMxARa29dIo2IApGISDDb8g4s/jNUFkJkIvT7B7S8wu6qRBodBSIRkWBUUQTLboUfp1jtpgNhwNsQ18rWskQaKwUiEZFgs+c7WHAlFG6wOkt3egA6Pwhh+soWqSv66xIRCRbGD+ufhe/vtTpRx7awjgqlnml3ZSKNngKRiEgwKMm15iHb8YXVbvF76PcaOJNtLUskVCgQiYjYbcdXkHWtNSlreLR1BVmbP2tSVpF6pEAkImIXXzmsfADWPWG13Z1g4LuQ2NneukRCkAKRiIgdCjdaYwvtWWq1294MPZ7SpKwiNlEgEhGpb5vfhCW3QGURRCVBv39Cxu/trkokpCkQiYjUl4pCKwhtectqp54JmW9BXIa9dYmIApGISL3YvcQ6RVa0yRpbqPME6HQfhIXbXZmIoEAkIlK3jB/WPQXf3wemEmJbwsBp1sjTIhI0FIhEROpKSY51OX3OLKudcQX0+7vVb0hEgooCkYhIXdg+E7KGQ9lOCI+BXs/BqTdqbCGRIKVAJCJSm3xl1umx9U9b7cQu1thC7o721iUiR6VAJCJSW7w/WJOy5i+32qeNhh5PWKNPi0hQUyASETlRxsDmqbB0NFTuBWcK9PsXtPid3ZWJSDUpEImInIhyDyy5GX56x2qnng0D3oLYk2wtS0SOjwKRiEhN7VpsnSLbuxkc4dD1EegwVmMLiTRACkQiIsfLGFj/DKwYa40tFHcyDJgGTTPtrkxEakiBSETkeJTthqzrYPvnVjvjCuj3D4hKtLMqETlBCkQiItW1c4E1/UbxNghzQq9noM1IjS0k0ggoEImIHIvxw9pJsPIBMD5IaAunvw9J3e2uTERqiQKRiMjRlO60pt/Y8YXVbnU19H0FIhPsrUtEapUCkYjIkeTOh4VXQ8l2a3DF3i/CKdfrFJlII6RAJCJyML8P1jwKqydYp8tcHaxTZImd7a5MROqIApGIyIFKcmDhHyF3ttVuPRz6TIaIOHvrEpE6pUAkIrJfzmxYOAxKcyE8Fvq8BKcMt7sqEakHYTV50NSpU5k+fXqgfc8995CYmMiAAQP46aefaq04EZF64a+EleNhzrlWGHJ3hvOXKgyJhJAaBaJHH32UmJgYALKyspg8eTKTJk2iSZMm3HHHHbVaoIhInSr+BeacA6v/Chg4dQQMXgzuDnZXJiL1qEanzLZt20abNm0A+Pjjj7n88su56aabGDhwIGeffXZt1iciUne2fwFZ10DZLoiIh75/h5OvsrsqEbFBjY4QxcfHs3v3bgC++uorzj33XACio6MpKSmpvepEROqCvwJWjIN5F1hhKKk7nL9MYUgkhNXoCNG5557LjTfeSI8ePfjhhx+48MILAVizZg2tWrWq1QJFRGrV3m3WDPW7FlrttrdAz6escYZEJGTV6AjR5MmTyczMZOfOnfznP/8hJSUFgGXLlnH11VfXaoEiIrXm589gZncrDEW64PQPrEvqFYZEQp7DGGNq8sDS0lJWrlxJXl4efr+/yrrf/e53tVJcsPB6vbjdbjweDy6Xy+5yROR4+crh+3Gw/mmrndwbTn8P4k+xty4RqVPH8/tdo1NmX3zxBddeey27d+/m4DzlcDjw+Xw1eVoRkdpXtNk6RbZ7sdVuNwa6PwbhTlvLEpHgUqNTZrfeeit/+MMf2L59O36/v8pNYUhEgsa2D2FmDysMRSbCmR9Dr2cUhkTkEDU6QpSbm8udd95JWlpabdcjInLifGWw/C744UWrndIfTn8X4nTRh4gcXo2OEF1xxRXMmzevlksREakFhRvhqwG/hqEO98C5XysMichR1ahTdXFxMX/4wx9o2rQpXbp0ITIyssr62267rdYKDAbqVC3SQPz0Piy6ESoLwZkC/d+Aky60uyoRsUmdd6p+5513+Oqrr4iOjmbevHk4HI7AOofD0egCkYgEucoS+O4O2Piq1W56Ogx8B2Jb2FuXiDQYNQpE999/Pw8//DD33nsvYWE1OusmIlI7CtZYV5F5VgMO6HQfdJkAYTX6ehOREFWjb4zy8nKGDh2qMCQi9jEGNrwMy/8CvlKIToXMt6DZuXZXJiINUI0SzfDhw3nvvfdquxYRkeop3QVfXwpLR1lhqNkFcMFKhSERqbEaHSHy+XxMmjSJL7/8kq5dux7Sqfrpp5+uleJERA6RM8eaob5kO4RFQffHod1t4NARaxGpuRoFolWrVtGjRw8AVq9eXWXdgR2sRURqjb8CVj4IaycBBlztrY7TSd3trkxEGoEaBaK5c+fWdh0iIkdWuBEWXA17lljtNjdBz2cgItbeukSk0dBlGCISvIyBzW/A0tFQWQRRSdDvNci4zO7KRKSRsfWk+8SJE+nTpw8JCQmkpqZy6aWXkp2dXWWb0tJSRo0aRUpKCvHx8Vx++eXk5uZW2Wbr1q0MGTKE2NhYUlNTufvuu6msrKyyzbx58+jZsydOp5M2bdowZcqUun57InIiyj2wcBh8e50VhlLPggu+VxgSkTphayCaP38+o0aN4ttvv2XWrFlUVFRw3nnnsXfv3sA2d9xxB5999hkffPAB8+fPZ/v27Vx22a9fiD6fjyFDhlBeXs7ChQuZOnUqU6ZMYfz48YFtNm/ezJAhQ/jNb37DihUrGDNmDDfeeCNffvllvb5fEammnVkwszv89A44wqHr/8FvZ0Ncht2ViUgjVaOpO+rKzp07SU1NZf78+Zx55pl4PB6aNm3KtGnTuOKKKwBYv349HTp0ICsri/79+zNz5kwuuugitm/fHphs9pVXXmHs2LHs3LmTqKgoxo4dy/Tp06t0AL/yyispKCjgiy++OGZdmrpDpJ74fbB2IqyaAMYHca1h4DRo0t/uykSkATqe3++guk7V4/EAkJycDMCyZcuoqKhg0KBBgW3at29Py5YtycrKAiArK4suXboEwhDA4MGD8Xq9rFmzJrDNgc+xf5v9z3GwsrIyvF5vlZuI1LG9W2H2b6wryYwPWl0NFyxXGBKRehE0gcjv9zNmzBgGDhxI586dAcjJySEqKorExMQq26alpZGTkxPY5sAwtH/9/nVH28br9VJSUnJILRMnTsTtdgduGRk6TC9Sp7b+B2Z0g53/g4h4yHwDBr4NUW67KxOREBE0gWjUqFGsXr2ad9991+5SGDduHB6PJ3Dbtm2b3SWJNE6Ve2HRTfDNFVBRACl94YIV0PoauysTkRATFJfdjx49ms8//5yvv/6aFi1+nZ06PT2d8vJyCgoKqhwlys3NJT09PbDN4sWLqzzf/qvQDtzm4CvTcnNzcblcxMTEHFKP0+nE6XTWynsTkSPYsxwWXgXebMABHe+Frg9DWOQxHyoiUttsPUJkjGH06NF89NFHzJkzh9atW1dZ36tXLyIjI5k9e3ZgWXZ2Nlu3biUzMxOAzMxMVq1aRV5eXmCbWbNm4XK56NixY2CbA59j/zb7n0NE6pHxw/pn4Kv+VhiKaQ6//S90f1RhSERsY+tVZrfccgvTpk3jk08+oV27doHlbrc7cOTm5ptvZsaMGUyZMgWXy8Wtt94KwMKFCwHrsvvu3bvTvHlzJk2aRE5ODtdccw033ngjjz76KGBddt+5c2dGjRrF9ddfz5w5c7jtttuYPn06gwcPPmaduspMpJaU5FrjCu3Yd3Vni0utgRadKXZWJSKN1PH8ftsaiI4079nrr7/OddddB1gDM/7lL3/hnXfeoaysjMGDB/PSSy8FTocB/PTTT9x8883MmzePuLg4hg8fzmOPPUZExK9nBOfNm8cdd9zB2rVradGiBQ8++GDgNY5FgUikFmyfaYWh0jwIj7am3mjzZ9D8hyJSRxpMIGooFIhEToCvDFaMheznrHZiFxj4Lrg72luXiDR6x/P7HRSdqkWkkfKsgwVXQcH3Vvu026DH49YRIhGRIKJAJCK1zxjY9A9YNgZ8JeBsAv2nwElD7K5MROSwFIhEpHaV7YHFI2Dbh1Y7/VzInAoxzeytS0TkKBSIRKT25M6HrD9C8c/WJfTdJkL7O8ARNGPAiogclgKRiJw4f4U1IeuaiYCBhLYw8B1I7mV3ZSIi1aJAJCInpnAjLBwGu/eNGH/K9dDrOYiMt7cuEZHjoEAkIjVjDGx+A5aOhsoiiEyEfv+AllfYXZmIyHFTIBKR41deAItHwtb3rHbqWZD5JsRl2FqWiEhNKRCJyPHJ+x8s/CMUbwVHOHR9BDqMhbBwuysTEakxBSIRqR5/Jax+BNb8zZqgNf4UGDANmvSzuzIRkROmQCQix1a02eo4vSvLarceDr1fgMgEe+sSEaklCkQicnSb34YlN0NlIUS6oc8rcPKVdlclIlKrFIhE5PDKPbB0FGx522o3HQgD3oa4VvbWJSJSBxSIRORQO7Ng4dWwd4vVcbrzeOh0H4TpK0NEGid9u4nIr/w+WPMorH4YjA/iTrY6TjfNtLsyEZE6pUAkIpa9P1mX0+/8xmqfPAx6T4Yot711iYjUAwUiEYEt78KSkVDhgYgE6PMStP6j3VWJiNQbBSKRUFZRCEtvhc1TrXZKfxj4tjXGkIhICFEgEglVuxZbHaeLNoEjDDrdb3WeVsdpEQlB+uYTCTV+H6x7HFY+BKYSYlvCgLcg9Qy7KxMRsY0CkUgo2bsNsq6BvPlWu+VQ6PsKRCXaWpaIiN0UiERCxdZ/w+KboDwfIuKsK8haXwsOh92ViYjYToFIpLGrKILvxsCmf1rt5D4wcBoktLG1LBGRYKJAJNKY7VkGC66Cwg2AAzreC10fhrBIuysTEQkqCkQijZHxw7onYeUD4K+A2BaQ+SaknW13ZSIiQUmBSKSxKf4Fsq6F3DlWO+Ny6Pt3cCbbW5eISBBTIBJpTLZ9CItGQPkeCI+F3s/DKder47SIyDEoEIk0Bod0nO5lTcrqOs3WskREGgoFIpGGbvdSa8TpQMfpsdDlYQiPsrsyEZEGQ4FIpKHy+2DdE7DyQWvE6ZiTYMCbkPYbuysTEWlwFIhEGqK926yO03nzrHbGFdD3VXWcFhGpIQUikYbm4BGne70Ap1ynjtMiIidAgUikoagogmW3wY+vW+3kPjDgbXC1tbcuEZFGQIFIpCHYtRgWDoOijYADOo2DLhM04rSISC1RIBIJZn4frHscVj5kdZyOzdg34vRZdlcmItKoKBCJBKu9WyHrGsj72mq3/H/Q9xWISrK3LhGRRkiBSCQY/fQ+LP4zVBRARDz0fhFaX6uO0yIidUSBSCSYVBTC0lth81SrndLX6jid0MbeukREGjkFIpFgsevbfR2nfwRHGHS8D7qMV8dpEZF6oEAkYje/D9Y8CqsfBuOD2JYw4C1IPcPuykREQoYCkYidirZYHad3fmO1W10JfV6GqEQ7qxIRCTkKRCJ22fIOLBkJFV6ISIA+k+HkP6rjtIiIDRSIROpbhReWjIItb1ntlP4w8G2IP8XeukREQpgCkUh92rkQFv4R9m62Ok53egA6Pwhh+lMUEbGTvoVF6oO/Etb8DVb/1eo4HdfKupy+6UC7KxMRERSIROpe0WbrqNCuhVa71dXQ5yWIcttbl4iIBCgQidSlzW/D0lsO6Dj9MrQeZndVIiJyEAUikbpQ7oElt8BP06x2kwHW2ELxre2tS0REDkuBSKS25X0DWX+EvT+BI9zqNN3pfnWcFhEJYvqGFqkt/gpY9QisfRSMH+Ja7+s4nWl3ZSIicgwKRCK1oXCTNQ/Z7kVWu/W10PsFiHTZW5eIiFSLApHIiTDGmpl+6a1QWQSRbujzCpx8pd2ViYjIcVAgEqmp8nxY/GfY+oHVTj0TMt+EuJb21iUiIsdNgUikJnLnWZOyFv8Mjgjo+gh0uAfCwu2uTEREakCBSOR4+Mph1XhYOwkwkNDW6jid0sfuykRE5AQoEIlUlzfb6ji9Z5nVPvVG6PkMRMbbW5eIiJwwBSKRYzEGNr0Gy8aArxiikqDvP6Dl5XZXJiIitUSBSORoSnfB4hHw88dWO+23kDkVYlvYWpaIiNQuBSKRI8n5L2RdCyU7ICwSuj0K7e8ER5jdlYmISC1TIBI5mK8Mvr8f1j9ltV3tYcA0SO5hb10iIlJnFIhEDuRZCwuuhoLvrXbbm6HHkxARa29dIiJSpxSIRMDqOL3hZVj+F/CVgrMJ9PsntPid3ZWJiEg9UCASKc2Db2+A7Z9b7fTzIHMKxDSztSwREak/CkQS2rZ/Ad9eB6W5EBYF3SdBu1vVcVpEJMQoEElo8pXC8rHww/NW293J6jid1NXeukRExBa2/m/w119/zcUXX0zz5s1xOBx8/PHHVdYbYxg/fjzNmjUjJiaGQYMGsWHDhirb7Nmzh2HDhuFyuUhMTOSGG26gqKioyjYrV67kjDPOIDo6moyMDCZNmlTXb02CWcEq+KLPr2HotNtg8BKFIRGREGZrINq7dy/dunVj8uTJh10/adIknn/+eV555RUWLVpEXFwcgwcPprS0NLDNsGHDWLNmDbNmzeLzzz/n66+/5qabbgqs93q9nHfeebRq1Yply5bxxBNPMGHCBP7+97/X+fuTIGMMZD9vhSHPaohOg7NnQO/nICLG7upERMROJkgA5qOPPgq0/X6/SU9PN0888URgWUFBgXE6neadd94xxhizdu1aA5glS5YEtpk5c6ZxOBzml19+McYY89JLL5mkpCRTVlYW2Gbs2LGmXbt21a7N4/EYwHg8npq+PbFb8Q5j5pxvzNtYt7lDjCnJtbsqERGpQ8fz+x20PUc3b95MTk4OgwYNCixzu93069ePrKwsALKyskhMTKR3796BbQYNGkRYWBiLFi0KbHPmmWcSFRUV2Gbw4MFkZ2eTn59/2NcuKyvD6/VWuUkDVroLvsqEHV9AeDT0ngxnfQbRqXZXJiIiQSJoA1FOTg4AaWlpVZanpaUF1uXk5JCaWvVHLSIiguTk5CrbHO45DnyNg02cOBG32x24ZWRknPgbEnv4K2HBUNi7BeJPgfOXwWm3gMNhd2UiIhJEgjYQ2WncuHF4PJ7Abdu2bXaXJDW1/B7InQMRcXDmJ+DuaHdFIiIShII2EKWnpwOQm5tbZXlubm5gXXp6Onl5eVXWV1ZWsmfPnirbHO45DnyNgzmdTlwuV5WbNECb34LsZ6z7/adCYmd76xERkaAVtIGodevWpKenM3v27MAyr9fLokWLyMzMBCAzM5OCggKWLVsW2GbOnDn4/X769esX2Obrr7+moqIisM2sWbNo164dSUlJ9fRupN7t+Q4Wj7Dud7ofWl5ubz0iIhLUbA1ERUVFrFixghUrVgBWR+oVK1awdetWHA4HY8aM4f/+7//49NNPWbVqFddeey3Nmzfn0ksvBaBDhw6cf/75jBgxgsWLF7NgwQJGjx7NlVdeSfPmzQG4+uqriYqK4oYbbmDNmjW89957PPfcc9x55502vWupc6U74evfW4MvNr8Qujxsd0UiIhLs6uGqtyOaO3euAQ65DR8+3BhjXXr/4IMPmrS0NON0Os0555xjsrOzqzzH7t27zVVXXWXi4+ONy+Uyf/rTn0xhYWGVbb7//ntz+umnG6fTaU466STz2GOPHVeduuy+AfGVGzPrbOvS+k/bGlOWb3dFIiJik+P5/XYYY4yNeaxB8Hq9uN1uPB6P+hMFu2VjIPs5iIiHwYvUiVpEJIQdz+930PYhEjluP75hhSGAzDcVhkREpNoUiKRx2L0UFu+bsqXzg5Bxqa3liIhIw6JAJA1fSS787/fgL4PmF0GXCXZXJCIiDYwCkTRs/gpY8P+g+GdwtYMBb4FDH2sRETk++uWQhu27OyHva4hIgDM+hii33RWJiEgDpEAkDdem1+GHF637A94Cd3t76xERkQZLgUgapl2LYclI636XCdDid7aWIyIiDZsCkTQ8JTnwv8vAXw4tLrGuKhMRETkBCkTSsPjK4Zs/QMkv4GoPmW+oE7WIiJww/ZJIw/LdHbDzG4h0wZkfW/+KiIicIAUiaTg2/RM2vAQ4YMDb1mX2IiIitUCBSBqGXd/Cklus+10ehpMusrceERFpVBSIJPiV7ID/Xb6vE/XvofP9dlckIiKNjAKRBDdfOfzvCijZbk3WmjlVnahFRKTW6ZdFgtuy22DXQoh0WyNRRybYXZGIiDRCCkQSvDb+HTa+itWJehq42tpdkYiINFIKRBKcdi6EpaOt+93+D0660N56RESkUVMgkuBTvH1fJ+oKyLgcOo6zuyIREWnkFIgkuPjKrDBUmgPuztB/CjgcdlclIiKNnAKRBA9jrNNku7+FyMR9I1HH212ViIiEAAUiCR4bX4VNrwEOGPgOJJxqd0UiIhIiFIgkOOxcYF1iD9DtUWh+vr31iIhISFEgEvsV//JrJ+qWf4COY+2uSEREQowCkdjLVwr/uwxKcyGxC/R/XZ2oRUSk3ikQiX2MgSWjYPdiiEqyOlFHxNldlYiIhCAFIrHPhpfhx39Zc5MNfBfiT7G7IhERCVEKRGKPvK9h2e3W/W6PQbPz7K1HRERCmgKR1L+92+CbP4CphFZXQoe77K5IRERCnAKR1K9AJ+o8SOwG/V5TJ2oREbGdApHUnwovZF0Le5ZCVDKc+ZE6UYuISFCIsLsACQHGwLb/WH2GSrZbnahPfw/iW9tdmYiICKBAJHWtaAssHQXbZ1jt+DbQ9xVIP8fWskRERA6kQCR1w18B65+BVRPAVwJhkdDxXug4DiJi7K5ORESkCgUiqX07s2DJn6FgldVOPQv6vAzuDvbWJSIicgQKRFJ7yvNhxb2w8e9W25kCPZ6E1sN1JZmIiAQ1BSI5ccbAT+/Ad3dYl9MDnPIn6D4JopvYW5uIiEg1KBDJiSncCEtugZxZVtvVHvq8Amln2VuXiIjIcVAgkprxlcHaSbDmb+AvgzAndH4AOtwN4U67qxMRETkuCkRy/HLnw5KR4F1vtdPPhT4vQUIbe+sSERGpIQUiqb7SXbDibvhxitWOToWez1rzkanTtIiINGAKRHJsxsDmqbD8LijbbS1r82foPhGikuytTUREglalvxJPqYeC0gI8Zda/BaUFgWUHLo+LjGPykMm21apAJEfnWW+dHsubb7UTu0CfV6Fppr11iYhInTLGUFJZUu1Ac7j1eyv2Vvv1msakKRBJEKosgTWPwrrHrVGnw2OhywRoP8YadVpERIJOpb+SovIiCssKrX/LC6vXPmC5p8wTCDQV/opaqSvCH0d4pZvw8kQoS8SUuPHtTaSyyI2/OBFKE/HFNIF7auXlalajfS8tQSvnv7D4ZijaaLWbD4HeL0L8ybaWJSLSWJVVlrG7ZDe7i3cH/vWWeasVYA5sl1aW1n5xJozwCjdh5YlQmoi/xI2vKBHK3FCaCKX7/z3SMheV/kgqj/EycRm1X/rxUCCSX5Xkwnd3wk/TrHZMc+j1PGRcpk7TIiLVYIyhqLyIXcW7Dgk4gWUHLN9VvIvdxbuP69RSdTj8UYRVxuMoT8CUx2NKE/CXxEN5ApTHQ9m+fw9pxx8QahKtYFMejw8HvsO8Tng4uN3gcu27pRxw/zC3hITDL4+Ph7CwWt0Fx02BSMD4YdNrsHwsVBSAIwzajoZuf4VIl93ViYjYoqyyDG+Zlz0lew4NNfuDzgGhZv/yGp9m8ocTXp4MJSmYvSn4i93VCDCHbxtf1GEDDFghJiHh11t8PCQk/3rf7T5ycDn4Fh3deP5/WYEo1BWsgsUjYddCq53UE/q+Cim97a1LRKQGyirLKCwvtE43lRUGTjsdqR24v+9fb2khnjIvReWFVPjLa15IRTSUpEBxEyhO2Xc/5ejLylz4zKGHSQ4OMAkJkJC6L8gcHGwSjr4sPr5xhZjapEAUqir3wqpHYP3TYCohIh66/h+cNgrC9LEQkfpVVlkW6Mx74L/7r1jylHkODTQHhBjvvhBTfiIh5khK3dUPNfvvV8RWOZ1U5d/0Iyx3HXT6ad8pJqdTAaY+6Jcv1PhKYeNr1tVjxT9by1r8Hno/D7Et7K1NRBqk/WPNHBxgDrvsgCuYCko9FJR48JZ7KPPVcmfg8ljr9FGZyzqdVOaqfrs8gYRIFwnOBFzR8SS6IqoGlrRjBxq3G2JiFGQaEgWiUFFZDBv/DusmQckOa1lsS+vqsRYX21ubiNjC5/f9epTlOG77g0x+SQHecg+lvuLaK6osvuqVSmXuX/8tcx010MSEJ+ByunBFx+NOiDikE29178fFKciEIgWixq6iCDa8DOufhNI8a1lsBnQaB6f8CcKj7a1PRI5bha+i+gGm3GsdkSnxUlDixVPqpbDcS1GFlxJf7V7ZRHls1QBzuFBzmGVx4Ym4otwkxrpIdIVXPdKS/mtYObiz74H34+OtvjYiNaVA1FhVeOGHybD+qV+n24g7GTrdB62HQ3iUreWJhCJjDMUVxXjLvFVOKXnLvFVOM+1fn1/iIb/YS37xvuXlHvZWeik3JbVbmC9yXzhxHftWnhAINZF+NwlRbtxON0kxbtwJkbjdh4aZo51WUpCRYKFA1NiUF0D2C5D9DJTnW8vi20Dn++HkYRplWuQEVPgqKCgtIL80nz0le8gvySe/NL9KiNkfbPbstYJMfqmHwjIvhRUeiiu9+I45PN1xKI+tXog54OZ0uIiLcO3rI+MiMdqFO94ZOOKSkAAJzQ5zVVPCoUdlnM7aeysidlMgaizK9kD2s5D9nHV0CMDVHjrdb81GryvHRACr34x19GVfqCnNDwSbXXv3kOvNZ1dRPjuLrMBTUJaPtyKfwso9lJmi2inCH7YvoLgPODJz5PtRfjfxkW4SolzW0ZhYN0mxCbgTIq2Q4jp8gDk4yOhojMiR6VeyoSvdaV06/8OLULnvy9rdCTo/CBlXQJi+/aRxMcawt2Lvr6eZSr3kebzkFHjI8eSTV7iHXXvz2VOcT0HpHjwV+RRV5LPXn08Jeyh3eMFhTqyIUheUJENpEpQkHTHMOMrdxIa7SYh043K6cEe7SY51k5wQh9vlsE4vpR56GungU0uROrArUucUiBqqklyro/QPL8H+KzwSu+0LQr+3RpsWCSIHBhlvmZddhR5yC7zkerzs9HrZVeQhf++BHX89FFV6KfZ5KfF7KcdLeZiHyvBCcPiPv4CD/yTK46xQU5L0a7DZF3LCypOIMcnEhiURH56EKyqJRGcySTFJpMS5SXRFkJD065GXg0PM/vu6Wkmk4VAgamiKt1uXzm981RpTCCC5F3QeDyddrG9fqRPlvvLA0ZicAg878j3kFnjYWehhd5GXPXutDsDeMg+F5V72VnrZ6/NQaqwgUxHmpTLCe/xBxsGRv6X84dYRmf1HZspdRFQk4fQn4zRJVpgJS8YVmYTbmURSdBIpccmkJiTRNCGRZHfUES+/Vt8YkdCjQNRQ7N0Gax+35hzzl1nLUvpZQaj5BQpCcljGGEoqS9hd5GH7HivI5BV4yfN62FXksYJMsQdP2b4jMhUeiv0eSoyHMrxUhHmojPBgwo9z0LzwfbfD8YdVGVMmrMJFpM9NlLE6/MaEuYkLdxEf5cIVZZ1mSop1kRLnokmCi1S3m1S3i6aJMbjdjkCY0SB4InIiFIiCXdEWWPsY/Pgv2D9hYNOB0PkhSB+kX4BGzO837PaW8MvufUdk8j3kefcdldnrIb+4IHApdlGFZ98RGQ+lePadWvLgj/RAeDWvanIAxxqNoTwuMHZMeKWLSL8bp3ETjZuYMBdxEW7iI60QkxjjJjnWRXKci6YJVohJS3KRmhiLy+UIHJGJ0LeQiAQBfRUFq8JNsOZR2PyGNdcYQOrZ0GW89a+CUNCqrASv17Azv5RfdnvIOTDIFHnYU/zrdAWFFQXsrbSOyJSaX4OML8KDcXogvBqzZkdw7L/kfVc1OcrdhFe4ifC7iPK7iXa4iQ1zExfhIj7SGk8mcf8RmXg3TV1uUl1u0pPcpCclkJwYoaMxItIoKRAFG282rP4b/DQNjM9aln6u1Vk69Qx7a2vkKiqgsBB2F1Twy679HX497Nrf4bfYS36JF2+ph8IKrxVk/F5KjMfqJxPutYJMlBecxzgyEwZE77sdiz8MR7mLsAo3ET7rEmzriEwiceHW5diuKOuITFKsm5S4ROuIzL4g0yzZRbPkeFwuh65WEhE5AgWiYFGwBtb8DX56F9h3SXCzC6wg1DTT1tKCmTFQXAxeL+R7KsnJLyS3wEOex8uuQi+7i6yrlqwOv959HX49FPu8lPJrPxlfhPfXIBN5lP4y4UDc8RToIKwigXCfmyifGyduYhyJxIa7iY+wgowr2hrlt0lcIk32BZlUt5tmyW5OSnGTmhhPeJiuGhQRqUsKRHbLXwmr/wrb/kMgCJ30OysIpfS2tbS6sj/EFBbuDzIV5OQXkufxkOf1sqfIy54DLr+2+sj8evl1GV7KHd59p5a84Nx3izrKBJP7TysdR5hxVMQR4XMR6XdZ/WQcLmLD3cRFWqP8Wv1kXCTHuWkS77KCTKKL9EQXzZLdNElwER8VT5iGQBARCXoKRHbKnQ+zz/61nXEZdHoAknvYVtLRGANFRVaI2ZVftu9oTCE7vYXs8nrZU1RIfnEhntJCvKWFFFUUsreikGJfIaX+QsoppCKskMqwQoj2/BpkIo8wL1MEEH/8dToqYwJBJsq4iHG4iQl3ERueEDgikxhtdfZtEu+mictFmts6tZSeZC1LcCYQodG9RURChr7x7dT0dHB1gMSu1lxjiV3q5GX2B5n8Ah+/7C7g59355HoK2On1sLuokPy9hRTsCzGF5d4qIaaMQiochVSGF+KPKISoQnAWHr2zb3X7xhwkrDLW6uxrXDhxERPm2ndqyZpzye107TsiY4WWpi4XqW4rxKS6XbijXbicLiLD1VFGRESOT0gFosmTJ/PEE0+Qk5NDt27deOGFF+jbt699BYWFw/nLICLmsKuNgZIS64iMx7P/yEw5O/LzyfVat917CwLzMHnL8ymqzKfYX0CpI58yRz4VEfn4o/IhugCivUevpwZBJqwylnBfApEmAScJRDsSiAlPIC4ygYSoBGu6gpgEkmITSI5LoInLuqUnuquEGJfTpSMyIiJim5D5BXrvvfe48847eeWVV+jXrx/PPvssgwcPJjs7m9TUVFtq+jmnlNGPLrVmyy7Lp7CygCJfPiVmX5gJL8A48yEmH6L3/Xu400vHGWQcFfFEVCRZY8iQgNORQGx4ArERCcRHJuCKTsAdnUDivhCTkpBAqstFamIC6ckJNElIwOVMID4qnnDNlSYiIo2AwxhzgrMcNgz9+vWjT58+vPjiiwD4/X4yMjK49dZbuffee4/6WK/Xi9vtxuPx4HK5aq2mpRu20mdaqxo9NqLSjdOfRDRJxIUlER+RhCsqkcToJJJjk2gSl0SaK4k0dxLNEhNp0SSJdHcSSTGJOqUkIiIh4Xh+v0PiCFF5eTnLli1j3LhxgWVhYWEMGjSIrKysQ7YvKyujrKws0PZ6j3GqqYZOTksimTbEhVmTR7qiEkmOSSIlNommCVaYSXcnkp6YRHJMEkkx1nxMLqdLR2ZERERqUUgEol27duHz+UhLS6uyPC0tjfXr1x+y/cSJE3n44YfrvK4mrgR2P7Shzl9HREREjk4DpBzGuHHj8Hg8gdu2bdvsLklERETqUEgcIWrSpAnh4eHk5uZWWZ6bm0t6evoh2zudTpxOZ32VJyIiIjYLiSNEUVFR9OrVi9mzZweW+f1+Zs+eTWampsUQEREJdSFxhAjgzjvvZPjw4fTu3Zu+ffvy7LPPsnfvXv70pz/ZXZqIiIjYLGQC0dChQ9m5cyfjx48nJyeH7t2788UXXxzS0VpERERCT8iMQ3Qi6mocIhEREak7x/P7HRJ9iERERESORoFIREREQp4CkYiIiIQ8BSIREREJeQpEIiIiEvIUiERERCTkKRCJiIhIyFMgEhERkZAXMiNVn4j9Y1d6vV6bKxEREZHq2v+7XZ0xqBWIqqGwsBCAjIwMmysRERGR41VYWIjb7T7qNpq6oxr8fj/bt28nISEBh8NxyHqv10tGRgbbtm3T1B5HoH1UPdpPx6Z9dGzaR9Wj/XRsDX0fGWMoLCykefPmhIUdvZeQjhBVQ1hYGC1atDjmdi6Xq0F+YOqT9lH1aD8dm/bRsWkfVY/207E15H10rCND+6lTtYiIiIQ8BSIREREJeQpEtcDpdPLQQw/hdDrtLiVoaR9Vj/bTsWkfHZv2UfVoPx1bKO0jdaoWERGRkKcjRCIiIhLyFIhEREQk5CkQiYiISMhTIDpBkydP5uSTTyY6Opp+/fqxePFiu0uqMxMmTMDhcFS5tW/fPrC+tLSUUaNGkZKSQnx8PJdffjm5ublVnmPr1q0MGTKE2NhYUlNTufvuu6msrKyyzbx58+jZsydOp5M2bdowZcqU+nh7NfL1119z8cUX07x5cxwOBx9//HGV9cYYxo8fT7NmzYiJiWHQoEFs2LChyjZ79uxh2LBhuFwuEhMTueGGGygqKqqyzcqVKznjjDOIjo4mIyODSZMmHVLLBx98QPv27YmOjqZLly7MmDGj1t9vTR1rP1133XWHfLbOP//8Kts09v00ceJE+vTpQ0JCAqmpqVx66aVkZ2dX2aY+/8aC8butOvvo7LPPPuSzNHLkyCrbNOZ99PLLL9O1a9fAuEGZmZnMnDkzsD7UP0NHZaTG3n33XRMVFWX+9a9/mTVr1pgRI0aYxMREk5uba3dpdeKhhx4ynTp1Mjt27Ajcdu7cGVg/cuRIk5GRYWbPnm2WLl1q+vfvbwYMGBBYX1lZaTp37mwGDRpkli9fbmbMmGGaNGlixo0bF9jmxx9/NLGxsebOO+80a9euNS+88IIJDw83X3zxRb2+1+qaMWOGuf/++82HH35oAPPRRx9VWf/YY48Zt9ttPv74Y/P999+b3/3ud6Z169ampKQksM35559vunXrZr799lvzv//9z7Rp08ZcddVVgfUej8ekpaWZYcOGmdWrV5t33nnHxMTEmFdffTWwzYIFC0x4eLiZNGmSWbt2rXnggQdMZGSkWbVqVZ3vg+o41n4aPny4Of/886t8tvbs2VNlm8a+nwYPHmxef/11s3r1arNixQpz4YUXmpYtW5qioqLANvX1Nxas323V2UdnnXWWGTFiRJXPksfjCaxv7Pvo008/NdOnTzc//PCDyc7ONvfdd5+JjIw0q1evNsboM3Q0CkQnoG/fvmbUqFGBts/nM82bNzcTJ060saq689BDD5lu3boddl1BQYGJjIw0H3zwQWDZunXrDGCysrKMMdaPYlhYmMnJyQls8/LLLxuXy2XKysqMMcbcc889plOnTlWee+jQoWbw4MG1/G5q38E/9H6/36Snp5snnngisKygoMA4nU7zzjvvGGOMWbt2rQHMkiVLAtvMnDnTOBwO88svvxhjjHnppZdMUlJSYB8ZY8zYsWNNu3btAu3/9//+nxkyZEiVevr162f+/Oc/1+p7rA1HCkSXXHLJER8TivspLy/PAGb+/PnGmPr9G2so320H7yNjrEB0++23H/ExobaPjDEmKSnJvPbaa/oMHYNOmdVQeXk5y5YtY9CgQYFlYWFhDBo0iKysLBsrq1sbNmygefPmnHLKKQwbNoytW7cCsGzZMioqKqrsj/bt29OyZcvA/sjKyqJLly6kpaUFthk8eDBer5c1a9YEtjnwOfZv0xD36ebNm8nJyanyftxuN/369auyTxITE+ndu3dgm0GDBhEWFsaiRYsC25x55plERUUFthk8eDDZ2dnk5+cHtmno+23evHmkpqbSrl07br75Znbv3h1YF4r7yePxAJCcnAzU399YQ/puO3gf7ff222/TpEkTOnfuzLhx4yguLg6sC6V95PP5ePfdd9m7dy+ZmZn6DB2D5jKroV27duHz+ap8aADS0tJYv369TVXVrX79+jFlyhTatWvHjh07ePjhhznjjDNYvXo1OTk5REVFkZiYWOUxaWlp5OTkAJCTk3PY/bV/3dG28Xq9lJSUEBMTU0fvrvbtf0+Hez8Hvt/U1NQq6yMiIkhOTq6yTevWrQ95jv3rkpKSjrjf9j9HsDv//PO57LLLaN26NZs2beK+++7jggsuICsri/Dw8JDbT36/nzFjxjBw4EA6d+4MUG9/Y/n5+Q3iu+1w+wjg6quvplWrVjRv3pyVK1cyduxYsrOz+fDDD4HQ2EerVq0iMzOT0tJS4uPj+eijj+jYsSMrVqzQZ+goFIik2i644ILA/a5du9KvXz9atWrF+++/36CCigSfK6+8MnC/S5cudO3alVNPPZV58+Zxzjnn2FiZPUaNGsXq1av55ptv7C4laB1pH910002B+126dKFZs2acc845bNq0iVNPPbW+y7RFu3btWLFiBR6Ph3//+98MHz6c+fPn211W0NMpsxpq0qQJ4eHhh/TOz83NJT093aaq6ldiYiKnnXYaGzduJD09nfLycgoKCqpsc+D+SE9PP+z+2r/uaNu4XK4GF7r2v6ejfUbS09PJy8ursr6yspI9e/bUyn5rqJ/FU045hSZNmrBx40YgtPbT6NGj+fzzz5k7dy4tWrQILK+vv7GG8N12pH10OP369QOo8llq7PsoKiqKNm3a0KtXLyZOnEi3bt147rnn9Bk6BgWiGoqKiqJXr17Mnj07sMzv9zN79mwyMzNtrKz+FBUVsWnTJpo1a0avXr2IjIyssj+ys7PZunVrYH9kZmayatWqKj9ss2bNwuVy0bFjx8A2Bz7H/m0a4j5t3bo16enpVd6P1+tl0aJFVfZJQUEBy5YtC2wzZ84c/H5/4Is8MzOTr7/+moqKisA2s2bNol27diQlJQW2aSz7DeDnn39m9+7dNGvWDAiN/WSMYfTo0Xz00UfMmTPnkNN/9fU3FszfbcfaR4ezYsUKgCqfpca8jw7H7/dTVlamz9Cx2N2ruyF79913jdPpNFOmTDFr1641N910k0lMTKzSO78x+ctf/mLmzZtnNm/ebBYsWGAGDRpkmjRpYvLy8owx1uWcLVu2NHPmzDFLly41mZmZJjMzM/D4/ZdznnfeeWbFihXmiy++ME2bNj3s5Zx33323WbdunZk8eXJQX3ZfWFholi9fbpYvX24A8/TTT5vly5ebn376yRhjXXafmJhoPvnkE7Ny5UpzySWXHPay+x49ephFixaZb775xrRt27bK5eQFBQUmLS3NXHPNNWb16tXm3XffNbGxsYdcTh4REWGefPJJs27dOvPQQw8FzeXkxhx9PxUWFpq77rrLZGVlmc2bN5v//ve/pmfPnqZt27amtLQ08ByNfT/dfPPNxu12m3nz5lW5ZLy4uDiwTX39jQXrd9ux9tHGjRvNI488YpYuXWo2b95sPvnkE3PKKaeYM888M/AcjX0f3XvvvWb+/Plm8+bNZuXKlebee+81DofDfPXVV8YYfYaORoHoBL3wwgumZcuWJioqyvTt29d8++23dpdUZ4YOHWqaNWtmoqKizEknnWSGDh1qNm7cGFhfUlJibrnlFpOUlGRiY2PN73//e7Njx44qz7FlyxZzwQUXmJiYGNOkSRPzl7/8xVRUVFTZZu7cuaZ79+4mKirKnHLKKeb111+vj7dXI3PnzjXAIbfhw4cbY6xL7x988EGTlpZmnE6nOeecc0x2dnaV59i9e7e56qqrTHx8vHG5XOZPf/qTKSwsrLLN999/b04//XTjdDrNSSedZB577LFDann//ffNaaedZqKiokynTp3M9OnT6+x9H6+j7afi4mJz3nnnmaZNm5rIyEjTqlUrM2LEiEO+OBv7fjrc/gGqfP7r828sGL/bjrWPtm7das4880yTnJxsnE6nadOmjbn77rurjENkTOPeR9dff71p1aqViYqKMk2bNjXnnHNOIAwZo8/Q0Wi2exEREQl56kMkIiIiIU+BSEREREKeApGIiIiEPAUiERERCXkKRCIiIhLyFIhEREQk5CkQiYiISMhTIBIREZGQp0AkInIE8+bNw+FwHDIZpog0PgpEIiIiEvIUiERERCTkKRCJSND797//TZcuXYiJiSElJYVBgwaxd+9eAF577TU6dOhAdHQ07du356WXXqry2MWLF9OjRw+io6Pp3bs3H330EQ6HgxUrVtSolm+++YYzzjiDmJgYMjIyuO222wK1AJx88sk8+uijXH/99SQkJNCyZUv+/ve/1/i9i0j9UCASkaC2Y8cOrrrqKq6//nrWrVvHvHnzuOyyyzDG8PbbbzN+/Hj+9re/sW7dOh599FEefPBBpk6dCkBRUREXXXQRHTt2ZNmyZUyYMIG77rqrxrVs2rSJ888/n8svv5yVK1fy3nvv8c033zB69Ogq2z311FP07t2b5cuXc8stt3DzzTeTnZ19QvtBROqYEREJYsuWLTOA2bJlyyHrTj31VDNt2rQqy/7617+azMxMY4wxr776qklJSTElJSWB9S+//LIBzPLly4/52nPnzjWAyc/PN8YYc8MNN5ibbrqpyjb/+9//TFhYWOA1WrVqZf74xz8G1vv9fpOammpefvnlar1fEbFHhM15TETkqLp168Y555xDly5dGDx4MOeddx5XXHEFUVFRbNq0iRtuuIERI0YEtq+srMTtdgOwbt06unbtSnR0dGB9ZmZmjWv5/vvvWblyJW+//XZgmTEGv9/P5s2b6dChAwBdu3YNrHc4HKSnp5OXl1fj1xWRuqdAJCJBLTw8nFmzZrFw4UK++uorXnjhBe6//34+++wzAP7xj3/Qr1+/Qx5TF4qKivjzn//Mbbfddsi6li1bBu5HRkZWWedwOPD7/XVSk4jUDgUiEQl6DoeDgQMHMnDgQMaPH0+rVq1YsGABzZs358cff2TYsGGHfVyHDh148803KS0tDRwl+vbbb2tcR8+ePVm7di1t2rSp8XOISHBSp2oRCWqLFi3i0UcfZenSpWzdupUPP/yQnTt30qFDBx5++GEmTpzI888/zw8//MCqVat4/fXXefrppwG4+uqrcTgcjBgxgrVr1zJjxgyefPLJGtcyduxYFi5cyOjRo1mxYgUbNmzgk08+OaRTtYg0PDpCJCJBzeVy8fXXX/Pss8/i9Xpp1aoVTz31FBdccAEAsbGxPPHEE9x9993ExcXRpUsXxowZA0B8fDyfffYZI0eOpEePHnTs2JHHH3+cyy+/vEa1dO3alfnz53P//fdzxhlnYIzh1FNPZejQobX1dkXEJg5jjLG7CBGR+rJlyxZat27N8uXL6d69u93liEiQ0CkzERERCXkKRCISskaOHEl8fPxhbyNHjrS7PBGpRzplJiIhKy8vD6/Xe9h1LpeL1NTUeq5IROyiQCQiIiIhT6fMREREJOQpEImIiEjIUyASERGRkKdAJCIiIiFPgUhERERCngKRiIiIhDwFIhEREQl5CkQiIiIS8v4/++VqC/dyPwYAAAAASUVORK5CYII=",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Causal Conv1d:\n",
      "    seq_len      Triton     Tri Dao        torch\n",
      "0    1024.0   25.880205   21.241609    74.020915\n",
      "1    3072.0   48.070356   47.190387   268.281072\n",
      "2    5120.0   69.957048   67.380138   514.207363\n",
      "3    7168.0   90.808071   89.288734  1007.277489\n",
      "4    9216.0  112.715423  111.557826  1288.639665\n",
      "5   11264.0  134.034723  134.974167  1570.576072\n",
      "6   13312.0  155.508786  157.792002  1853.861213\n",
      "7   15360.0  176.871449  179.818884  2138.824224\n",
      "8   17408.0  197.943777  206.493899  2433.331490\n",
      "9   19456.0  220.543534  229.233876  2720.437765\n",
      "10  21504.0  240.761772  250.865519  3003.364086\n",
      "11  23552.0  263.211668  274.355263  3291.127205\n",
      "12  25600.0  284.336656  295.164198  3575.031996\n",
      "13  27648.0  306.770325  324.826986  3864.300728\n",
      "14  29696.0  326.481551  344.143093  4155.583858\n",
      "15  31744.0  348.030150  368.262738  4441.213131\n"
     ]
    }
   ],
   "source": [
    "\n",
    "torch.cuda.empty_cache()\n",
    "@triton.testing.perf_report(\n",
    "    triton.testing.Benchmark(\n",
    "        x_names=['seq_len'],  # argument names to use as an x-axis for the plot\n",
    "        x_vals=[1024 * i for i in range(1, 32+1, 2)],  # different possible values for `x_name`\n",
    "        line_arg='provider',  # argument name whose value corresponds to a different line in the plot\n",
    "        line_vals=['Triton', 'Tri Dao', 'torch'],  # possible values for `line_arg``\n",
    "        line_names=[\n",
    "            \"Triton\",\n",
    "            \"Tri Dao\",\n",
    "            \"torch\"\n",
    "        ],  # label name for the lines\n",
    "        styles=[('blue', '-'), ('green', '-'), ('orange', '-')],  # line styles\n",
    "        ylabel=\"ms\",  # label name for the y-axis\n",
    "        plot_name=\"Causal Conv1d\",  # name for the plot. Used also as a file name for saving the plot.\n",
    "        args={'dim': 1024, 'bs': 4, 'k':4}\n",
    "        # args={'bs': 2, 'num_head': 32, 'rope_head_dim': 32, \n",
    "        #       'nope_head_dim': 64, 'kv_lora_rank': 256},  # values for function arguments not in `x_names` and `y_name`\n",
    "    ))\n",
    "def benchmark(bs, seq_len, dim, k, provider):\n",
    "    device = torch.device('cuda')\n",
    "    dtype = torch.float16\n",
    "    x = torch.randn(bs, seq_len, dim).to(device).to(dtype)\n",
    "    conv = torch.nn.Conv1d(dim, dim, kernel_size=k, groups=dim, padding=k-1, bias=False).to(dtype).to(device)\n",
    "    stream = torch.cuda.Stream()\n",
    "    torch.cuda.set_stream(stream)\n",
    "    \n",
    "    if provider == 'Triton':\n",
    "        ms = triton.testing.do_bench(lambda: triton_causal_conv1d(x.transpose(-1, -2), conv.weight.squeeze()))\n",
    "    if provider == 'Tri Dao' and k <=4:\n",
    "        ms = triton.testing.do_bench(lambda: causal_conv1d_fn(x.transpose(-1, -2), conv.weight.squeeze()))\n",
    "    if provider == 'torch':\n",
    "        ms = triton.testing.do_bench(lambda: conv(x.transpose(-1, -2))[..., :seq_len])\n",
    "\n",
    "    return ms * 1e3\n",
    "benchmark.run(show_plots=True, print_data=True)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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j6d169a89tprNd53EakbCkQiEtR2797Ntddeyy233MLGjRtZsGABV155JcYY3nrrLSZOnMhf//pXNm7cyKOPPspDDz3E9OnTAcjPz+eyyy6jU6dOrFixgkmTJnHvvffWuJatW7dy8cUXM3jwYNasWcO7777L119/zahRo/z6Pfnkk/Tp04eVK1dyxx13cPvtt5OZmXlSx0FEapkREQliK1asMIDZvn37EetOP/108/bbb/st+/Of/2zS0tKMMca8+uqrpkmTJqaoqMi3/uWXXzaAWbly5XG/9/z58w1gDhw4YIwxZtiwYea2227z6/O///3PhIWF+b5HmzZtzPXXX+9b7/V6TVJSknn55Zertb8iYo8Im/OYiMgxde/enQEDBtC1a1fS09O56KKLuOqqq3A6nWzdupVhw4YxfPhwX//y8nISEhIA2LhxI926dSMqKsq3Pi0trca1rF69mjVr1vDWW2/5lhlj8Hq9bNu2jY4dOwLQrVs333qHw0FKSgp79uyp8fcVkdqnQCQiQS08PJw5c+awePFi/vvf//L888/zwAMP8MknnwDw97//ndTU1CM+Uxvy8/P54x//yF133XXEutatW/veR0ZG+q1zOBx4vd5aqUlEAkOBSESCnsPhoH///vTv35+JEyfSpk0bFi1aRIsWLfj+++8ZMmRIlZ/r2LEj//znPykuLvadJVqyZEmN6+jVqxcbNmygXbt2Nd6GiAQnDaoWkaC2dOlSHn30Ub755ht27NjB+++/z969e+nYsSOPPPIIU6ZM4bnnnuO7775j7dq1vPHGGzz11FMAXHfddTgcDoYPH86GDRuYPXs2TzzxRI1rGTduHIsXL2bUqFGsWrWKzZs389FHHx0xqFpE6h+dIRKRoOZ2u/nqq6945pln8Hg8tGnThieffJJLLrkEgJiYGB5//HHGjh1LbGwsXbt2ZfTo0QDExcXxySefMGLECHr27EmnTp3429/+xuDBg2tUS7du3Vi4cCEPPPAA55xzDsYYTj/9dK6++upA7a6I2MRhjDF2FyEiUle2b99O27ZtWblyJT169LC7HBEJErpkJiIiIiFPgUhEQtaIESOIi4ur8jVixAi7yxOROqRLZiISsvbs2YPH46lyndvtJikpqY4rEhG7KBCJiIhIyNMlMxEREQl5CkQiIiIS8hSIREREJOQpEImIiEjIUyASERGRkKdAJCIiIiFPgUhERERCngKRiIiIhLz/B/Q/Riu87KNiAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Causal Conv1d:\n",
      "    seq_len      Triton     Tri Dao        torch\n",
      "0    1024.0   28.956262   20.817684    92.708021\n",
      "1    3072.0   53.499877   46.179403   331.010073\n",
      "2    5120.0   77.993989   66.185839   613.604069\n",
      "3    7168.0  101.455159   88.692307  1145.984054\n",
      "4    9216.0  124.580003  112.367406  1464.869142\n",
      "5   11264.0  148.964688  135.913149  1784.900904\n",
      "6   13312.0  171.655387  158.484757  2104.265451\n",
      "7   15360.0  196.621031  180.365369  2436.499357\n",
      "8   17408.0  220.248118  202.474654  2758.360386\n",
      "9   19456.0  243.898794  226.400942  3080.976486\n",
      "10  21504.0  267.497987  252.925843  3406.471014\n",
      "11  23552.0  290.502727  275.763303  3728.728533\n",
      "12  25600.0  314.088374  301.272720  4051.842690\n",
      "13  27648.0  337.208807  324.408323  4380.102158\n",
      "14  29696.0  360.067606  351.377726  4706.009865\n",
      "15  31744.0  384.631664  365.042865  5029.458523\n"
     ]
    }
   ],
   "source": [
    "\n",
    "torch.cuda.empty_cache()\n",
    "@triton.testing.perf_report(\n",
    "    triton.testing.Benchmark(\n",
    "        x_names=['seq_len'],  # argument names to use as an x-axis for the plot\n",
    "        x_vals=[1024 * i for i in range(1, 32+1, 2)],  # different possible values for `x_name`\n",
    "        line_arg='provider',  # argument name whose value corresponds to a different line in the plot\n",
    "        line_vals=['Triton', 'Tri Dao', 'torch'],  # possible values for `line_arg``\n",
    "        line_names=[\n",
    "            \"Triton\",\n",
    "            \"Tri Dao\",\n",
    "            \"torch\"\n",
    "        ],  # label name for the lines\n",
    "        styles=[('blue', '-'), ('green', '-'), ('orange', '-')],  # line styles\n",
    "        ylabel=\"ms\",  # label name for the y-axis\n",
    "        plot_name=\"Causal Conv1d\",  # name for the plot. Used also as a file name for saving the plot.\n",
    "        args={'dim': 1024, 'bs': 4, 'k':4}\n",
    "        # args={'bs': 2, 'num_head': 32, 'rope_head_dim': 32, \n",
    "        #       'nope_head_dim': 64, 'kv_lora_rank': 256},  # values for function arguments not in `x_names` and `y_name`\n",
    "    ))\n",
    "def benchmark(bs, seq_len, dim, k, provider):\n",
    "    device = torch.device('cuda')\n",
    "    dtype = torch.float16\n",
    "    x = torch.randn(bs, seq_len, dim).to(device).to(dtype)\n",
    "    conv = torch.nn.Conv1d(dim, dim, kernel_size=k, groups=dim, padding=k-1, bias=False).to(dtype).to(device)\n",
    "    stream = torch.cuda.Stream()\n",
    "    torch.cuda.set_stream(stream)\n",
    "    \n",
    "    if provider == 'Triton':\n",
    "        ms = triton.testing.do_bench(lambda: triton_causal_conv1d(x.transpose(-1, -2), conv.weight.squeeze(), None, 'silu'))\n",
    "    if provider == 'Tri Dao' and k <=4:\n",
    "        ms = triton.testing.do_bench(lambda: causal_conv1d_fn(x.transpose(-1, -2), conv.weight.squeeze(), activation='silu'))\n",
    "    if provider == 'torch':\n",
    "        ms = triton.testing.do_bench(lambda: torch.nn.SiLU()(conv(x.transpose(-1, -2))[..., :seq_len]))\n",
    "\n",
    "    return ms * 1e3\n",
    "benchmark.run(show_plots=True, print_data=True)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# backward"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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tSsrV67kiSeeYPr06bz66qvN+nlFRKSNqimH7+6GOedA2XZolwqjFsLAxyAw1O7qpJbxY6NHjzY33HCD12u//vWvzZgxY4wxxrjdbpOYmGieeOIJz/aioiITGhpq3n77bWOMMRs2bDCAWb58uWefL774wjgcDpObm2uMMeall14ysbGxpqKiwrPP5MmTTY8ePXyutbi42ACmuLj4iG379+83GzZsMPv37/f5eNL49N9BRPzSnlXGfNrXmDexHktuNKbSZXdVbUZ9f78P59c9TWeccQZz5szhhx9+AOD777/n22+/5YILLgBg69at5OfnM2rUKM97oqOjGTZsGBkZGQBkZGQQExPD0KFDPfuMGjWKgIAAli5d6tln5MiRhISEePZJT08nMzOTvXv31llbRUUFLpfL69EQozH3ttL5FxG/4q6B9TPgq9OheB2ExcPIj2HY3yA4yu7qpA5+PRB8ypQpuFwuevbsSWBgIDU1NTzyyCOMGTMGgPz8fAASEhK83peQkODZlp+fT3x8vNf2oKAg2rdv77VPamrqEceo3RYbG3tEbTNmzODBBx/06XPU3la/b98+n2bGlqZRWVkJ4LVUi4iILTRRZYvk16Hp3//+N2+++SZvvfUWffr0YfXq1UyaNInk5GTGjh1ra21Tp07lzjvv9LRdLhcpKSl17hsYGEhMTIxnHbWIiAivtduk6bndbnbu3ElERARBQX79Yy8irZkxsOU1+O4Oa96loCgY+hykjtW8Sy2AX//1uOeee5gyZQpXXnklAP369WP79u3MmDGDsWPHkpiYCEBBQQFJSUme9xUUFDBw4EAAEhMTj1j0tbq6mj179njen5iYSEFBgdc+te3afQ4XGhpKaKjvg/Nqj6MFaO0TEBBAly5dFFhFxB77C2DpjZD3qdWOHwnD34DIbraWJb7z69C0b9++I5bmCAwM9CzimpqaSmJiInPmzPGEJJfLxdKlS7nlllsASEtLo6ioiJUrVzJkyBAA5s6di9vtZtiwYZ597r33Xq910WbPnk2PHj3qvDR3PBwOB0lJScTHx1NVVdUox5RjExISUu9SLyIiTeaIiSofhZ53gEO/k1oSvw5NF198MY888ghdunShT58+rFq1iqeffpobbrgBsILIpEmTePjhhznllFNITU3l/vvvJzk5mUsvvRSwFpg9//zzGT9+PK+88gpVVVVMnDiRK6+8kuTkZACuvvpqHnzwQcaNG8fkyZNZt24dzz77LH/5y18a/TMFBgZqTI2ISFtRWWxNVLn1DautiSpbtia/l+8EuFwuc/vtt5suXbqYsLAwc9JJJ5l7773Xa2oAt9tt7r//fpOQkGBCQ0PNueeeazIzM72Os3v3bnPVVVeZyMhI43Q6zfXXX29KSkq89vn+++/NmWeeaUJDQ02nTp3MY489dky1HsstiyIi0gbkzzPmgy4HphJwGLNqsjHV5XZXJYc5lr/fWnuukRzL2jUiItKK1ZTD9/fBpqcBY01UmfZPiD/T7sqkDlp7TkRExA57V8Pia6x5lwBOvhEGP615l1oJhSYREZET5a6BjTNh7QPgrrImqjz9Neh8sd2VSSNSaBIRETkRJVusiSp3LbbanX8Fp/9VE1W2QgpNIiIix8MY+Okf1t1xnokqn4fUazVRZSul0CQiInKsynfBsvGQ86HV1kSVbYJCk4iIyLHY8TUsuQ7274CAYOj/CPS8EwI0B19rp9AkIiLii5pyWDUZfnjOajt7wRlvQvtB9tYlzUahSUREpCF718DiMQenEjhlAgyaCUER9tYlzUqhSURE5GiMGzKfhdVTwF1pTSUw7HXodKHdlYkNFJpERETqsi/XGruU/43V7nQxDHvNCk7SJik0iYiIHC7rv7DsJqjcA4HhMPgv0P0mTSXQxik0iYiI1KoqseZd+ul1q91+iDXY29nD3rrELyg0iYiIAOxaAot/B6VbAAf0ngL9pkNgiN2ViZ9QaBIRkbbNXQ3rH4F1fwZTAxFd4Ix/WRNWihxCoUlERNquki1W79LuJVa769Vw2osQEmNrWeKfFJpERKTtMQa2vgErboXqUgh2wmkvQ7er7a5M/JhCk4iItC0Vu2HZzZD9H6sdPxLS/gntutpbl/g9hSYREWk78r+BjLGwPw8cQdD/z9DrHq0bJz5RaBIRkdavpgK+/xNsetpqO3scWDduiL11SYui0CQiIq1b0XpYfDUUrbHa3W+GwU9CUDt765IWR6FJRERaJ+OGH16AVX8EdwWEdoRhf4fOF9tdmbRQCk0iItL67N8BS66HHV9Z7eQLYdg/IDzB3rqkRVNoEhGR1iX7Q1h2o3WXXGAYDHoKTrlF68bJCVNoEhGR1qG6DFbeAVv+ZrVjB1mDvaN72VuXtBoKTSIi4r9qKqxFdKtLobrEen54u7oEqkqteZdKNgMOaxqB/n/WunHSqBSaRESkcVXsgaoiK8gcHmyOpV1dCu6qY/veEZ2tiSoTzmmSjyZtm0KTiIg0npV3QOYzjX/cwHAIioTgKAiKOvD1kHZQJEQkQ/ebICS28b+/CApNIiLSWLI/PBiYAiMOCTiRdQedo7WP2BYJAfpzJfbTT6GIiJy48l2w/PfW896TYeBj9tYj0gQC7C5ARERagRUTobwQontDv+l2VyPSJBSaRETkxGS9B1nvgiMQhr9hzY0k0gopNImIyPErL4Tlf7Ce954KcUPtrUekCSk0iYjI8TEGlt8CFbsgpj/0vd/uikSalEKTiIgcn+3vQPb74AiCtDc0kaS0egpNIiJy7PbvgBUTrOd974fYgbaWI9IcFJpEROTYGAPLfg+VeyF2MPSZandFIs1CoUlERI7N1n9B7icQEGxdlgsItrsikWah0CQiIr7blwMrb7Oe93sQYvraW49IM1JoEhER3xgDS8dDVTHEnQ697rG7IpFmpdAkIiK++ekfsONLCAiF4bO0Hpy0OQpNIiLSsLLtsPIO6/mAhyG6l731iNhAoUlEROpnDCwZB9Ul0OEM6HGH3RWJ2EKhSURE6vfjK1AwBwLDYfjrEBBod0UitlBoEhGRoyv9CVYdGPA9YAY4T7W3HhEbKTSJiEjdjBuW3ADVZRA/EnrcandFIrZSaBIRkbr98AIULoCgdtZlOYf+ZEjbpn8BIiJyJNdmWD3Fej5wJkSeZG89In5AoUlERLy5a2Dp9VCzHxLOhVNutrsiEb+g0CQiIt4yn4GdiyAoEob/XZflRA7QvwQRETmoeBN8f6/1fPDT0K6rvfWI+BGFJhERsbirYclYcFdAUjqcfKPdFYn4FYUmERGxbHwSdi+D4GgY9ho4HHZXJOJXFJpERASK1sHaB6znQ56FiM721iPihxSaRETaOncVZIwFdyUkXwSp19pdkYhfUmgSEWnr1j8Ge7+DkFgY9qouy4kchUKTiEhbtnc1rHvIej70BQhPsrUcEX+m0CQi0lbVVFqX5Uw1dP4VdL3K7opE/JpCk4hIW7X+YShaA6FxcNrLuiwn0gCFJhGRtmj3Clj/qPX8tJchPMHeekRaAIUmEZG2pqbCmsTS1ECX30KX39hdkUiL4PehKTc3l9/97nfExcURHh5Ov379WLFihWe7MYZp06aRlJREeHg4o0aNYvPmzV7H2LNnD2PGjMHpdBITE8O4ceMoLS312mfNmjWcddZZhIWFkZKSwsyZM5vl84mINLu106F4A4TFw9AX7a5GpMXw69C0d+9eRowYQXBwMF988QUbNmzgqaeeIjY21rPPzJkzee6553jllVdYunQp7dq1Iz09nfLycs8+Y8aMYf369cyePZtPP/2UhQsXctNNN3m2u1wuzjvvPLp27crKlSt54oknmD59Oq+++mqzfl4RkSa3awlsPPA/hae9AmEd7K1HpCUxfmzy5MnmzDPPPOp2t9ttEhMTzRNPPOF5raioyISGhpq3337bGGPMhg0bDGCWL1/u2eeLL74wDofD5ObmGmOMeemll0xsbKypqKjw+t49evTwudbi4mIDmOLiYp/fIyLSrKr2GfNJD2PexJhFY+yuRsQvHMvfb7/uafr4448ZOnQov/nNb4iPj2fQoEH87W9/82zfunUr+fn5jBo1yvNadHQ0w4YNIyMjA4CMjAxiYmIYOnSoZ59Ro0YREBDA0qVLPfuMHDmSkJAQzz7p6elkZmayd+/eOmurqKjA5XJ5PURE/Nqa+8GVac3FNOQ5u6sRaXH8OjT99NNPvPzyy5xyyil89dVX3HLLLdx222288cYbAOTn5wOQkOB910dCQoJnW35+PvHx8V7bg4KCaN++vdc+dR3j0O9xuBkzZhAdHe15pKSknOCnFRFpQoXfwqanreen/w1C29tbj0gL5Nehye12M3jwYB599FEGDRrETTfdxPjx43nllVfsLo2pU6dSXFzseWRnZ9tdkohI3arLYMn1gIGTrodOo+2uSKRF8uvQlJSURO/evb1e69WrF1lZWQAkJiYCUFBQ4LVPQUGBZ1tiYiKFhYVe26urq9mzZ4/XPnUd49DvcbjQ0FCcTqfXQ0TEL62eCqU/QkRnGPy03dWItFh+HZpGjBhBZmam12s//PADXbt2BSA1NZXExETmzJnj2e5yuVi6dClpaWkApKWlUVRUxMqVKz37zJ07F7fbzbBhwzz7LFy4kKqqKs8+s2fPpkePHl536omItDgF8+GH563np78GITF2ViPSovl1aLrjjjtYsmQJjz76KD/++CNvvfUWr776KhMmTADA4XAwadIkHn74YT7++GPWrl3LtddeS3JyMpdeeilg9Uydf/75jB8/nmXLlrFo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zz//3Nx7773m/fffN4D54IMPvLY/9thjJjo62nz44Yfm+++/N7/85S9Namqq2b9/v2ef888/3wwYMMAsWbLE/O9//zPdu3c3V111lWd7cXGxSUhIMGPGjDHr1q0zb7/9tgkPDzd//etfPfssWrTIBAYGmpkzZ5oNGzaY++67zwQHB5u1a9c2+TloSEPnaOzYseb888/3+rnas2eP1z6t/Rylp6eb119/3axbt86sXr3aXHjhhaZLly6mtLTUs09z/fvy599rvpynn/3sZ2b8+PFeP0/FxcWe7a39PH388cfms88+Mz/88IPJzMw0f/rTn0xwcLBZt26dMUY/R/VRaGpip59+upkwYYKnXVNTY5KTk82MGTNsrKrpPPDAA2bAgAF1bisqKjLBwcHmvffe87y2ceNGA5iMjAxjjPXHMyAgwOTn53v2efnll43T6TQVFRXGGGP++Mc/mj59+ngd+4orrjDp6emN/Gka3+GBwO12m8TERPPEE094XisqKjKhoaHm7bffNsYYs2HDBgOY5cuXe/b54osvjMPhMLm5ucYYY1566SUTGxvrOUfGGDN58mTTo0cPT/u3v/2tGT16tFc9w4YNM7///e8b9TOeqKOFpksuueSo72lr58gYYwoLCw1gFixYYIxp3n9fLen32uHnyRgrNN1+++1HfU9bPE+xsbHmtdde089RA3R5rglVVlaycuVKRo0a5XktICCAUaNGkZGRYWNlTWvz5s0kJydz0kknMWbMGLKysgBYuXIlVVVVXuejZ8+edOnSxXM+MjIy6NevHwkJCZ590tPTcblcrF+/3rPPoceo3aclntOtW7eSn5/v9Xmio6MZNmyY1zmJiYlh6NChnn1GjRpFQEAAS5cu9ewzcuRIQkJCPPukp6eTmZnJ3r17Pfu05PM2f/584uPj6dGjB7fccgu7d+/2bGuL56i4uBiA9u3bA83376ul/V47/DzVevPNN+nQoQN9+/Zl6tSp7Nu3z7OtLZ2nmpoa3nnnHcrKykhLS9PPUQO0YG8T2rVrFzU1NV4/WAAJCQls2rTJpqqa1rBhw5g1axY9evRgx44dPPjgg5x11lmsW7eO/Px8QkJCiImJ8XpPQkIC+fn5AOTn59d5vmq31bePy+Vi//79hIeHN9Gna3y1n6muz3Po542Pj/faHhQURPv27b32SU1NPeIYtdtiY2OPet5qj+HPzj//fH7961+TmprKli1b+NOf/sQFF1xARkYGgYGBbe4cud1uJk2axIgRI+jbty9As/372rt3b4v5vVbXeQK4+uqr6dq1K8nJyaxZs4bJkyeTmZnJ+++/D7SN87R27VrS0tIoLy8nMjKSDz74gN69e7N69Wr9HNVDoUka1QUXXOB53r9/f4YNG0bXrl3597//3aLCjPiXK6+80vO8X79+9O/fn5NPPpn58+dz7rnn2liZPSZMmMC6dev49ttv7S7Frx3tPN10002e5/369SMpKYlzzz2XLVu2cPLJJzd3mbbo0aMHq1evpri4mP/85z+MHTuWBQsW2F2W39PluSbUoUMHAgMDj7jroKCggMTERJuqal4xMTGceuqp/PjjjyQmJlJZWUlRUZHXPoeej8TExDrPV+22+vZxOp0tLpjVfqb6fkYSExMpLCz02l5dXc2ePXsa5by1xJ/Fk046iQ4dOvDjjz8CbescTZw4kU8//ZR58+bRuXNnz+vN9e+rpfxeO9p5qsuwYcMAvH6eWvt5CgkJoXv37gwZMoQZM2YwYMAAnn32Wf0cNUChqQmFhIQwZMgQ5syZ43nN7XYzZ84c0tLSbKys+ZSWlrJlyxaSkpIYMmQIwcHBXucjMzOTrKwsz/lIS0tj7dq1Xn8AZ8+ejdPppHfv3p59Dj1G7T4t8ZympqaSmJjo9XlcLhdLly71OidFRUWsXLnSs8/cuXNxu92eX/ZpaWksXLiQqqoqzz6zZ8+mR48exMbGevZpLectJyeH3bt3k5SUBLSNc2SMYeLEiXzwwQfMnTv3iEuNzfXvy99/rzV0nuqyevVqAK+fp9Z+ng7ndrupqKjQz1FD7B6J3tq98847JjQ01MyaNcts2LDB3HTTTSYmJsbrroPW5K677jLz5883W7duNYsWLTKjRo0yHTp0MIWFhcYY61bWLl26mLlz55oVK1aYtLQ0k5aW5nl/7a2s5513nlm9erX58ssvTceOHeu8lfWee+4xGzduNC+++KJfTzlQUlJiVq1aZVatWmUA8/TTT5tVq1aZ7du3G2OsKQdiYmLMRx99ZNasWWMuueSSOqccGDRokFm6dKn59ttvzSmnnOJ1O31RUZFJSEgw11xzjVm3bp155513TERExBG30wcFBZknn3zSbNy40TzwwAN+czt9feeopKTE3H333SYjI8Ns3brVfPPNN2bw4MHmlFNOMeXl5Z5jtPZzdMstt5jo6Ggzf/58r1vl9+3b59mnuf59+fPvtYbO048//mgeeughs2LFCrN161bz0UcfmZNOOsmMHDnSc4zWfp6mTJliFixYYLZu3WrWrFljpkyZYhwOh/n666+NMfo5qo9CUzN4/vnnTZcuXUxISIg5/fTTzZIlS+wuqclcccUVJikpyYSEhJhOnTqZK664wvz444+e7fv37zd/+MMfTGxsrImIiDC/+tWvzI4dO7yOsW3bNnPBBReY8PBw06FDB3PXXXeZqqoqr33mzZtnBg4caEJCQsxJJ51kXn/99eb4eMdl3rx5BjjiMXbsWGOMNe3A/fffbxISEkxoaKg599xzTWZmptcxdu/eba666ioTGRlpnE6nuf76601JSYnXPt9//70588wzTWhoqOnUqZN57LHHjqjl3//+tzn11FNNSEiI6dOnj/nss8+a7HMfi/rO0b59+8x5551nOnbsaIKDg03Xrl3N+PHjj/jF2trPUV3nB/D62W/Of1/++nutofOUlZVlRo4cadq3b29CQ0NN9+7dzT333OM1T5Mxrfs83XDDDaZr164mJCTEdOzY0Zx77rmewGSMfo7q4zDGmObr1xIRERFpmTSmSURERMQHCk0iIiIiPlBoEhEREfGBQpOIiIiIDxSaRERERHyg0CQiIiLiA4UmERERER8oNImInID58+fjcDiOWKtLRFofhSYRERERHyg0iYiIiPhAoUlEWoX//Oc/9OvXj/DwcOLi4hg1ahRlZWUAvPbaa/Tq1YuwsDB69uzJSy+95PXeZcuWMWjQIMLCwhg6dCgffPABDoeD1atXH1ct3377LWeddRbh4eGkpKRw2223eWoB6NatG48++ig33HADUVFRdOnShVdfffW4P7uINA+FJhFp8Xbs2MFVV13FDTfcwMaNG5k/fz6//vWvMcbw5ptvMm3aNB555BE2btzIo48+yv33388bb7wBQGlpKRdddBG9e/dm5cqVTJ8+nbvvvvu4a9myZQvnn38+l112GWvWrOHdd9/l22+/ZeLEiV77PfXUUwwdOpRVq1bxhz/8gVtuuYXMzMwTOg8i0sRsXjBYROSErVy50gBm27ZtR2w7+eSTzVtvveX12p///GeTlpZmjDHmr3/9q4mLizP79+/3bH/55ZcNYFatWtXg9543b54BzN69e40xxowbN87cdNNNXvv873//MwEBAZ7v0bVrV/O73/3Os93tdpv4+Hjz8ssv+/R5RcQeQTZnNhGREzZgwADOPfdc+vXrR3p6Oueddx6XX345ISEhbNmyhXHjxjF+/HjP/tXV1URHRwOwceNG+vfvT1hYmGd7Wlracdfy/fffs2bNGt58803Pa8YY3G43W7dupVevXgD079/fs93hcJCYmEhhYeFxf18RaXoKTSLS4gUGBjJ79mwWL17M119/zfPPP8+9997LJ598AsDf/vY3hg0bdsR7mkJpaSm///3vue22247Y1qVLF8/z4OBgr20OhwO3290kNYlI41BoEpFWweFwMGLECEaMGMG0adPo2rUrixYtIjk5mZ9++okxY8bU+b5evXrxr3/9i/Lyck9v05IlS467jsGDB7Nhwwa6d+9+3McQEf+kgeAi0uItXbqURx99lBUrVpCVlcX777/Pzp076dWrFw8++CAzZszgueee44cffmDt2rW8/vrrPP300wBcffXVOBwOxo8fz4YNG/j888958sknj7uWyZMns3jxYiZOnMjq1avZvHkzH3300REDwUWk5VFPk4i0eE6nk4ULF/LMM8/gcrno2rUrTz31FBdccAEAERERPPHEE9xzzz20a9eOfv36MWnSJAAiIyP55JNPuPnmmxk0aBC9e/fm8ccf57LLLjuuWvr378+CBQu49957OeusszDGcPLJJ3PFFVc01scVEZs4jDHG7iJERPzJtm3bSE1NZdWqVQwcONDuckTET+jynIiIiIgPFJpEROpx8803ExkZWefj5ptvtrs8EWlGujwnIlKPwsJCXC5XnducTifx8fHNXJGI2EWhSURERMQHujwnIiIi4gOFJhEREREfKDSJiIiI+EChSURERMQHCk0iIiIiPlBoEhEREfGBQpOIiIiIDxSaRERERHzw/5aziOEHnSJYAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Causal Conv1d:\n",
      "    seq_len       Triton      Tri Dao        torch\n",
      "0    1024.0   128.298774    66.945113   223.350450\n",
      "1    3072.0   172.790989   129.397810   700.261354\n",
      "2    5120.0   270.155400   205.824956  1256.106734\n",
      "3    7168.0   368.904144   296.655059  2037.152767\n",
      "4    9216.0   468.177825   393.200547  2736.416817\n",
      "5   11264.0   566.641927   485.238791  3354.240894\n",
      "6   13312.0   665.129423   573.894143  3956.538439\n",
      "7   15360.0   764.203191   662.824810  4633.635044\n",
      "8   17408.0   862.925053   770.540953  5218.553066\n",
      "9   19456.0   961.605966   855.936646  5777.555943\n",
      "10  21504.0  1060.444355   953.077316  7038.701057\n",
      "11  23552.0  1158.129215  1040.545583  7080.910206\n",
      "12  25600.0  1257.471561  1138.653874  7681.005001\n",
      "13  27648.0  1356.268764  1234.436631  8268.675804\n",
      "14  29696.0  1455.133677  1326.674223  8893.651962\n",
      "15  31744.0  1554.681659  1428.699136  9558.150291\n"
     ]
    }
   ],
   "source": [
    "torch.cuda.empty_cache()\n",
    "@triton.testing.perf_report(\n",
    "    triton.testing.Benchmark(\n",
    "        x_names=['seq_len'],  # argument names to use as an x-axis for the plot\n",
    "        x_vals=[1024 * i for i in range(1, 32+1, 2)],  # different possible values for `x_name`\n",
    "        line_arg='provider',  # argument name whose value corresponds to a different line in the plot\n",
    "        line_vals=['Triton', 'Tri Dao', 'torch'],  # possible values for `line_arg``\n",
    "        line_names=[\n",
    "            \"Triton\",\n",
    "            \"Tri Dao\",\n",
    "            \"torch\"\n",
    "        ],  # label name for the lines\n",
    "        styles=[('blue', '-'), ('green', '-'), ('orange', '-')],  # line styles\n",
    "        ylabel=\"ms\",  # label name for the y-axis\n",
    "        plot_name=\"Causal Conv1d\",  # name for the plot. Used also as a file name for saving the plot.\n",
    "        args={'dim': 1024, 'bs': 4, 'k':4}\n",
    "        # args={'bs': 2, 'num_head': 32, 'rope_head_dim': 32, \n",
    "        #       'nope_head_dim': 64, 'kv_lora_rank': 256},  # values for function arguments not in `x_names` and `y_name`\n",
    "    ))\n",
    "def benchmark(bs, seq_len, dim, k, provider):\n",
    "    device = torch.device('cuda')\n",
    "    dtype = torch.float16\n",
    "    x = torch.randn(bs, seq_len, dim).to(device).to(dtype)\n",
    "    x.requires_grad_(True)\n",
    "    dy = torch.rand_like(x).transpose(-1, -2)\n",
    "    conv = torch.nn.Conv1d(dim, dim, kernel_size=k, groups=dim, padding=k-1, bias=False).to(dtype).to(device)\n",
    "    stream = torch.cuda.Stream()\n",
    "    torch.cuda.set_stream(stream)\n",
    "    ms = 1\n",
    "    if provider == 'Triton':\n",
    "        y = triton_causal_conv1d(x.transpose(-1, -2), conv.weight.squeeze(), None, 'silu')\n",
    "        ms = triton.testing.do_bench(lambda: y.backward(dy, retain_graph=True))\n",
    "    if provider == 'Tri Dao' and k <=4:\n",
    "        y = causal_conv1d_fn(x.transpose(-1, -2), conv.weight.squeeze(), activation='silu')\n",
    "        ms = triton.testing.do_bench(lambda: y.backward(dy, retain_graph=True))\n",
    "    if provider == 'torch':\n",
    "        y = conv(x.transpose(-1, -2))[..., :seq_len]\n",
    "        ms = triton.testing.do_bench(lambda: y.backward(dy, retain_graph=True))\n",
    "\n",
    "    return ms * 1e3\n",
    "benchmark.run(show_plots=True, print_data=True)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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Hz8OwNdff81vf/tbAAICAsjLy6ux4pxOJ/369ePJJ5+kb9++jB07ljFjxjB37twae49z9dBDD5GRkeF6HDx40O6SRMQOBcdh5Y2wagQUnYSIATDkO+h8Nzg8etioiJylc5py4Le//S133HEHffv25fvvv+eKK64AYNu2bbRp06bGiouNjaVbt25uz3Xt2pX//ve/AMTEWFefpKWlERsb61onLS2NPn36uNY5cuSI2zaKi4tJT093vT4mJoa0tDS3dUrbpeuczt/fH39//3PcMxFpEH5aAKvvgPxUcHhDj6nQ/WHw8rW7MhGpBef0v0Evv/wyCQkJHD16lP/+9780a9YMgPXr13PTTTfVWHEXXnghKSkpbs99//33rmAWHx9PTEwMixYtci3PzMxk9erVJCQkAJCQkMDJkydZv369a53FixfjdDoZNGiQa53ly5dTVFTkWmfhwoV07tzZ7Uo9EREAirJg9VhYdqUVmEK7wGXfQs9HFZhEGjCHMcacywvz8/PZvHkzR44cwel0ui373e9+VyPFrV27lgsuuIDHHnuMP/zhD6xZs4YxY8bw6quvMmLECACeeuopZs6cyfz584mPj2fq1Kls3ryZ7du3ExAQAMDll19OWloac+fOpaioiNtvv50BAwbw1ltvAZCRkUHnzp257LLLmDRpElu3bmXUqFH87W9/c5uaoCqZmZmEhYWRkZFBaGhojey/iHigI99YE1Xm7LPane+D3k+AT6C9dYnIOTmr7+9zuTzviy++MJGRkcbLy8s4HA63h5eX17lsslKffvqp6dGjh/H39zddunQxr776qttyp9Nppk6daqKjo42/v7+59NJLTUpKits6x48fN8OHDzchISEmNDTU3H777SYrK8ttnU2bNpmLLrrI+Pv7mxYtWpiZM2eeVZ2ackCkgSvOM+a7B41502FNJfBha2NSF9tdlYj8Qmfz/X1OPU0dO3bksssuY9q0aeWuOmus1NMk0oCd2AirboGMrVa73W3Q73nwC7OxKBGpCWfz/X1OA8HT0tKYOHGiApOINGzOYtgxC7ZMt2b19o+EQa9Z944TkUbnnELTDTfcwNKlS2nfvn1N1yMi4hkyd0HyrXD8W6vd8hoY+HcIiKryZSLScJ3T6bnc3Fx+//vfExkZSc+ePfH1db9a5O67766xAusLnZ4TaSCMgV1zYMOD1qze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t27d2Lhxoz5HVVBokhp1+eWXu37v1asXgwYNok2bNrz33nv1KsyIZxk2bJjr9549e9KrVy/at2/P0qVLufTSS22szB7jx49n69atrFixwu5SPFplx2ns2LGu33v27ElsbCyXXnope/bsoX379nVdpi06d+7Mxo0bycjI4D//+Q8jR45k2bJldpfl8XR6rhY1b94cb2/vclcdpKWlERMTY1NVdSs8PJxOnTqxe/duYmJiKCws5OTJk27rlD0eMTExFR6v0mVVrRMaGlrvglnpPlX1GYmJieHIkSNuy4uLi0lPT6+R41YfP4vt2rWjefPm7N69G2hcx2jChAl89tlnLFmyhJYtW7qer6t/X/Xl71plx6kigwYNAnD7PDX04+Tn50eHDh3o378/M2bMoHfv3syePVufozNQaKpFfn5+9O/fn0WLFrmeczqdLFq0iISEBBsrqzvZ2dns2bOH2NhY+vfvj6+vr9vxSElJ4cCBA67jkZCQwJYtW9y+ABcuXEhoaCjdunVzrVN2G6Xr1MdjGh8fT0xMjNv+ZGZmsnr1ardjcvLkSdavX+9aZ/HixTidTtcf+4SEBJYvX05RUZFrnYULF9K5c2eaNm3qWqehHLcff/yR48ePExsbCzSOY2SMYcKECXz44YcsXry43KnGuvr35el/1850nCqyceNGALfPU0M/TqdzOp0UFBToc3Qmdo9Eb+jeeecd4+/vb+bNm2e2b99uxo4da8LDw92uOmhI7r//frN06VKzb98+s3LlSpOYmGiaN29ujhw5YoyxLmVt3bq1Wbx4sVm3bp1JSEgwCQkJrteXXsp62WWXmY0bN5ovv/zSREZGVngp64MPPmh27NhhXn75ZY+eciArK8ts2LDBbNiwwQDmueeeMxs2bDA//PCDMcaaciA8PNx8/PHHZvPmzebqq6+ucMqBvn37mtWrV5sVK1aYjh07ul1Of/LkSRMdHW1uueUWs3XrVvPOO++YoKCgcpfT+/j4mGeeecbs2LHDPProox5zOX1VxygrK8s88MADJjk52ezbt8/873//M/369TMdO3Y0+fn5rm009GM0btw4ExYWZpYuXep2qXxubq5rnbr69+XJf9fOdJx2795t/vKXv5h169aZffv2mY8//ti0a9fOXHzxxa5tNPTjNHnyZLNs2TKzb98+s3nzZjN58mTjcDjM119/bYzR56gqCk114MUXXzStW7c2fn5+ZuDAgebbb7+1u6Rac+ONN5rY2Fjj5+dnWrRoYW688Uaze/du1/K8vDzzpz/9yTRt2tQEBQWZa6+91hw+fNhtG/v37zeXX365CQwMNM2bNzf333+/KSoqcltnyZIlpk+fPsbPz8+0a9fOvPHGG3Wxe+dkyZIlBij3GDlypDHGmnZg6tSpJjo62vj7+5tLL73UpKSkuG3j+PHjZvjw4SYkJMSEhoaa22+/3WRlZbmts2nTJnPRRRcZf39/06JFCzNz5sxytbz33numU6dOxs/Pz3Tv3t0sWLCg1vb7bFR1jHJzc81ll11mIiMjja+vr2nTpo0ZM2ZMuT+sDf0YVXR8ALfPfl3++/LUv2tnOk4HDhwwF198sYmIiDD+/v6mQ4cO5sEHH3Sbp8mYhn2cRo0aZdq0aWP8/PxMZGSkufTSS12ByRh9jqriMMaYuuvXEhEREamfNKZJREREpBoUmkRERESqQaFJREREpBoUmkRERESqQaFJREREpBoUmkRERESqQaFJREREpBoUmkREfoGlS5ficDjK3atLRBoehSYRERGRalBoEhEREakGhSYRaRD+85//0LNnTwIDA2nWrBmJiYnk5OQA8I9//IOuXbsSEBBAly5deOWVV9xeu2bNGvr27UtAQAADBgzgww8/xOFwsHHjxnOqZcWKFQwePJjAwEBatWrF3Xff7aoFoG3btjz55JOMGjWKJk2a0Lp1a1599dVz3ncRqRsKTSJS7x0+fJjhw4czatQoduzYwdKlS7nuuuswxvDmm28ybdo0nnjiCXbs2MGTTz7J1KlTmT9/PgDZ2dlceeWVdOvWjfXr1zN9+nQeeOCBc65lz549DBkyhOuvv57Nmzfz7rvvsmLFCiZMmOC23rPPPsuAAQPYsGEDf/rTnxg3bhwpKSm/6DiISC2z+YbBIiK/2Pr16w1g9u/fX25Z+/btzVtvveX23OOPP24SEhKMMcb8/e9/N82aNTN5eXmu5XPmzDGA2bBhwxnfe8mSJQYwJ06cMMYYM3r0aDN27Fi3db755hvj5eXleo82bdqYm2++2bXc6XSaqKgoM2fOnGrtr4jYw8fmzCYi8ov17t2bSy+9lJ49e5KUlMRll13GDTfcgJ+fH3v27GH06NGMGTPGtX5xcTFhYWEA7Nixg169ehEQEOBanpCQcM61bNq0ic2bN/Pmm2+6njPG4HQ62bdvH127dgWgV69eruUOh4OYmBiOHDlyzu8rIrVPoUlE6j1vb28WLlzIqlWr+Prrr3nxxReZMmUKn376KQCvvfYagwYNKvea2pCdnc0f//hH7r777nLLWrdu7frd19fXbZnD4cDpdNZKTSJSMxSaRKRBcDgcXHjhhVx44YVMmzaNNm3asHLlSuLi4ti7dy8jRoyo8HVdu3bl3//+N/n5+a7epm+//fac6+jXrx/bt2+nQ4cO57wNEfFMGgguIvXe6tWrefLJJ1m3bh0HDhzggw8+4OjRo3Tt2pXHHnuMGTNm8MILL/D999+zZcsW3njjDZ577jkAbrrpJhwOB2PGjGH79u18/vnnPPPMM+dcy6RJk1i1ahUTJkxg48aN7Nq1i48//rjcQHARqX/U0yQi9V5oaCjLly/n+eefJzMzkzZt2vDss89y+eWXAxAUFMTTTz/Ngw8+SHBwMD179uTee+8FICQkhE8//ZQ777yTvn370q1bN5566imuv/76c6qlV69eLFu2jClTpjB48GCMMbRv354bb7yxpnZXRGziMMYYu4sQEfEk+/fvJz4+ng0bNtCnTx+7yxERD6HTcyIiIiLVoNAkIlKFO++8k5CQkAofd955p93liUgd0uk5EZEqHDlyhMzMzAqXhYaGEhUVVccViYhdFJpEREREqkGn50RERESqQaFJREREpBoUmkRERESqQaFJREREpBoUmkRERESqQaFJREREpBoUmkRERESqQaFJREREpBr+H9dmmN7kwgHPAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Causal Conv1d:\n",
      "    seq_len       Triton      Tri Dao         torch\n",
      "0    1024.0   139.250576    58.117930    268.710792\n",
      "1    3072.0   252.920747   131.033927    846.082270\n",
      "2    5120.0   401.665926   209.337294   1500.859857\n",
      "3    7168.0   551.090360   299.748033   2383.734941\n",
      "4    9216.0   696.215272   389.910936   3187.000513\n",
      "5   11264.0   846.010149   482.431769   3923.974752\n",
      "6   13312.0   992.531896   583.816230   4608.646393\n",
      "7   15360.0  1133.202910   671.427429   5376.945496\n",
      "8   17408.0  1275.299668   770.325243   6087.050438\n",
      "9   19456.0  1421.623468   866.928339   6778.948307\n",
      "10  21504.0  1568.641186   964.792907   7551.707268\n",
      "11  23552.0  1720.397949  1064.802051   8367.506027\n",
      "12  25600.0  1876.276135  1148.567438   9087.285995\n",
      "13  27648.0  2023.428917  1246.405721   9890.815735\n",
      "14  29696.0  2163.791656  1330.713153  10715.231895\n",
      "15  31744.0  2305.154324  1432.516098  11603.472710\n"
     ]
    }
   ],
   "source": [
    "torch.cuda.empty_cache()\n",
    "@triton.testing.perf_report(\n",
    "    triton.testing.Benchmark(\n",
    "        x_names=['seq_len'],  # argument names to use as an x-axis for the plot\n",
    "        x_vals=[1024 * i for i in range(1, 32+1, 2)],  # different possible values for `x_name`\n",
    "        line_arg='provider',  # argument name whose value corresponds to a different line in the plot\n",
    "        line_vals=['Triton', 'Tri Dao', 'torch'],  # possible values for `line_arg``\n",
    "        line_names=[\n",
    "            \"Triton\",\n",
    "            \"Tri Dao\",\n",
    "            \"torch\"\n",
    "        ],  # label name for the lines\n",
    "        styles=[('blue', '-'), ('green', '-'), ('orange', '-')],  # line styles\n",
    "        ylabel=\"ms\",  # label name for the y-axis\n",
    "        plot_name=\"Causal Conv1d\",  # name for the plot. Used also as a file name for saving the plot.\n",
    "        args={'dim': 1024, 'bs': 4, 'k':4}\n",
    "        # args={'bs': 2, 'num_head': 32, 'rope_head_dim': 32, \n",
    "        #       'nope_head_dim': 64, 'kv_lora_rank': 256},  # values for function arguments not in `x_names` and `y_name`\n",
    "    ))\n",
    "def benchmark(bs, seq_len, dim, k, provider):\n",
    "    device = torch.device('cuda')\n",
    "    dtype = torch.float16\n",
    "    x = torch.randn(bs, seq_len, dim).to(device).to(dtype)\n",
    "    x.requires_grad_(True)\n",
    "    dy = torch.rand_like(x).transpose(-1, -2)\n",
    "    conv = torch.nn.Conv1d(dim, dim, kernel_size=k, groups=dim, padding=k-1, bias=False).to(dtype).to(device)\n",
    "    stream = torch.cuda.Stream()\n",
    "    torch.cuda.set_stream(stream)\n",
    "    ms = 1\n",
    "    if provider == 'Triton':\n",
    "        y = triton_causal_conv1d(x.transpose(-1, -2), conv.weight.squeeze(), None, 'silu')\n",
    "        ms = triton.testing.do_bench(lambda: y.backward(dy, retain_graph=True))\n",
    "    if provider == 'Tri Dao' and k <=4:\n",
    "        y = causal_conv1d_fn(x.transpose(-1, -2), conv.weight.squeeze(), activation='silu')\n",
    "        ms = triton.testing.do_bench(lambda: y.backward(dy, retain_graph=True))\n",
    "    if provider == 'torch':\n",
    "        y = conv(x.transpose(-1, -2))[..., :seq_len]\n",
    "        y = torch.nn.SiLU()(y)\n",
    "        ms = triton.testing.do_bench(lambda: y.backward(dy, retain_graph=True))\n",
    "\n",
    "    return ms * 1e3\n",
    "benchmark.run(show_plots=True, print_data=True)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# replace"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/mnt/workspace/mdy/miniforge/envs/mdy/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n",
      "/mnt/workspace/mdy/miniforge/envs/mdy/lib/python3.10/site-packages/_distutils_hack/__init__.py:53: UserWarning: Reliance on distutils from stdlib is deprecated. Users must rely on setuptools to provide the distutils module. Avoid importing distutils or import setuptools first, and avoid setting SETUPTOOLS_USE_DISTUTILS=stdlib. Register concerns at https://github.com/pypa/setuptools/issues/new?template=distutils-deprecation.yml\n",
      "  warnings.warn(\n"
     ]
    }
   ],
   "source": [
    "from transformers import Mamba2ForCausalLM, Mamba2Config\n",
    "import torch\n",
    "import importlib\n",
    "import causal_conv1d_triton"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "config = Mamba2Config()\n",
    "config.hidden_size = 1024\n",
    "config.num_heads = 16\n",
    "config.num_hidden_layers = 2\n",
    "config.n_groups = 8\n",
    "config.head_dim = 128\n",
    "config.vocab_size = 512\n",
    "config.conv_kernel = 8\n",
    "model = Mamba2ForCausalLM(config).to(torch.bfloat16).cuda() \n",
    "\n",
    "for idx,p in enumerate(model.parameters()):\n",
    "    if idx == 9:\n",
    "        break\n",
    "def zero_grad():\n",
    "    for p in model.parameters():\n",
    "        if p.grad is not None:\n",
    "            p.grad.zero_()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "x = torch.arange(128).reshape(2,64).cuda()\n",
    "dy = torch.randn(2, 64, config.vocab_size).to(model.dtype).to(model.device)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[[ 4.8633e-01, -9.6094e-01, -4.0820e-01,  ..., -8.9844e-02,\n",
      "           2.0898e-01,  3.7842e-02],\n",
      "         [ 2.8711e-01,  9.0234e-01, -2.6123e-02,  ...,  2.7930e-01,\n",
      "           5.2734e-01,  5.8594e-01],\n",
      "         [ 1.2012e-01,  8.3984e-01,  8.0859e-01,  ...,  7.6953e-01,\n",
      "           3.1445e-01, -1.2085e-02],\n",
      "         ...,\n",
      "         [ 1.4453e+00,  7.1289e-02, -4.1797e-01,  ..., -7.0312e-01,\n",
      "          -7.0312e-02,  3.8281e-01],\n",
      "         [ 1.2598e-01,  2.2070e-01,  2.4707e-01,  ...,  1.2500e+00,\n",
      "          -7.1875e-01, -6.0938e-01],\n",
      "         [-8.0078e-01, -1.8848e-01, -1.3281e-01,  ..., -1.8555e-01,\n",
      "          -7.6562e-01,  1.6113e-01]],\n",
      "\n",
      "        [[-2.5977e-01, -6.4844e-01, -1.1250e+00,  ..., -1.5723e-01,\n",
      "           1.3047e+00,  8.3008e-03],\n",
      "         [-3.7109e-01, -4.7266e-01, -3.9844e-01,  ..., -5.5859e-01,\n",
      "          -6.4844e-01, -6.2500e-01],\n",
      "         [-7.8125e-01, -2.5940e-04,  2.6172e-01,  ..., -6.9922e-01,\n",
      "          -2.9297e-01, -4.3457e-02],\n",
      "         ...,\n",
      "         [-9.4922e-01,  2.4414e-01, -1.1016e+00,  ...,  4.4531e-01,\n",
      "           3.7305e-01, -6.6833e-03],\n",
      "         [ 9.8438e-01, -3.9062e-01, -7.7734e-01,  ...,  2.2949e-01,\n",
      "           1.7969e-01, -6.0938e-01],\n",
      "         [ 2.9102e-01, -9.4141e-01, -4.6875e-02,  ..., -2.7539e-01,\n",
      "          -4.4922e-02,  1.1133e-01]]], device='cuda:0',\n",
      "       grad_fn=<ToCopyBackward0>)\n",
      "tensor([[ 11.2500,   0.8633,  -5.0938,  ...,   7.9062,   6.1562,  -4.5000],\n",
      "        [-12.1875,   5.6875, -14.0000,  ...,  -1.0156,   5.7188,  -1.2266],\n",
      "        [  2.0156,  -5.5625,   9.1875,  ..., -11.4375,   5.9688,   2.1406],\n",
      "        ...,\n",
      "        [  0.7812,  -0.0742,  10.2500,  ...,  -3.8438,  18.6250,  -2.3750],\n",
      "        [  8.5000,   0.5312,  -1.3281,  ..., -12.1250, -13.6250,   4.7812],\n",
      "        [ -8.3125,  -4.9688,   5.3125,  ...,  -2.6875,  -3.2031,  -2.7969]],\n",
      "       device='cuda:0', dtype=torch.bfloat16)\n"
     ]
    }
   ],
   "source": [
    "out1 = model(x).logits\n",
    "out1.backward(dy)\n",
    "print(out1)\n",
    "print(p.grad)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/mnt/workspace/mdy/miniforge/envs/mdy/lib/python3.10/site-packages/_distutils_hack/__init__.py:53: UserWarning: Reliance on distutils from stdlib is deprecated. Users must rely on setuptools to provide the distutils module. Avoid importing distutils or import setuptools first, and avoid setting SETUPTOOLS_USE_DISTUTILS=stdlib. Register concerns at https://github.com/pypa/setuptools/issues/new?template=distutils-deprecation.yml\n",
      "  warnings.warn(\n",
      "/mnt/workspace/mdy/miniforge/envs/mdy/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    }
   ],
   "source": [
    "from replace_conv1d_cuda import trigger"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[[-7.5391e-01, -4.0625e-01, -3.6133e-01,  ..., -2.2949e-02,\n",
      "          -1.9727e-01, -1.6895e-01],\n",
      "         [-2.6758e-01, -6.9922e-01,  4.5312e-01,  ...,  5.7422e-01,\n",
      "           1.2793e-01, -9.4141e-01],\n",
      "         [-2.9492e-01, -4.6680e-01,  5.1562e-01,  ..., -1.1562e+00,\n",
      "          -5.7031e-01, -5.1953e-01],\n",
      "         ...,\n",
      "         [-3.1738e-03,  7.1484e-01, -7.6172e-01,  ...,  6.2500e-01,\n",
      "           3.0859e-01,  2.0605e-01],\n",
      "         [ 3.1250e-01, -5.8203e-01,  6.8750e-01,  ..., -3.3008e-01,\n",
      "           7.5000e-01,  3.2227e-01],\n",
      "         [-5.5469e-01, -5.1562e-01,  3.3398e-01,  ...,  9.0332e-02,\n",
      "           3.3398e-01, -1.0234e+00]],\n",
      "\n",
      "        [[ 7.7148e-02,  4.2969e-01,  1.0986e-01,  ..., -8.5156e-01,\n",
      "           3.9648e-01,  1.0000e+00],\n",
      "         [ 5.0391e-01,  4.0039e-01,  4.5654e-02,  ...,  1.2793e-01,\n",
      "          -9.4727e-02,  9.9609e-02],\n",
      "         [-7.9102e-02,  2.8320e-01,  1.4375e+00,  ..., -6.8359e-01,\n",
      "           1.2012e-01,  7.7438e-04],\n",
      "         ...,\n",
      "         [ 6.8359e-02, -2.4121e-01,  4.8828e-01,  ..., -2.8125e-01,\n",
      "          -3.1250e-01, -2.4805e-01],\n",
      "         [ 5.5078e-01, -4.6680e-01, -7.1289e-02,  ...,  1.0859e+00,\n",
      "          -6.0547e-01, -8.1250e-01],\n",
      "         [ 8.1250e-01, -5.5078e-01,  2.0898e-01,  ..., -9.1797e-01,\n",
      "           3.6328e-01,  1.6602e-01]]], device='cuda:0',\n",
      "       grad_fn=<ToCopyBackward0>)\n",
      "tensor([[ -8.5625,   6.5938,  -1.6719,  ...,  19.7500,  -7.6875,  10.6250],\n",
      "        [ 10.5625,  -1.4062, -12.3750,  ...,  42.2500,  -9.3750,  -2.0938],\n",
      "        [  4.7812,   0.3379,  -2.8906,  ...,  -8.5000,   8.7500,  -5.6250],\n",
      "        ...,\n",
      "        [-12.0625,   7.8125,  -5.6875,  ...,   3.4062, -10.5625,  -3.0156],\n",
      "        [  1.8750,   8.2500,  14.9375,  ...,  14.6250,  21.0000,  -0.7773],\n",
      "        [  3.2188,  -1.9297,  -9.1250,  ...,  -6.5938, -17.6250,  12.6250]],\n",
      "       device='cuda:0', dtype=torch.bfloat16)\n"
     ]
    }
   ],
   "source": [
    "zero_grad()\n",
    "out2 = model(x).logits\n",
    "out2.backward(dy)\n",
    "print(out2)\n",
    "print(p.grad)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[0., 0., 0.,  ..., 0., 0., 0.],\n",
       "        [0., 0., 0.,  ..., 0., 0., 0.],\n",
       "        [0., 0., 0.,  ..., 0., 0., 0.],\n",
       "        ...,\n",
       "        [0., 0., 0.,  ..., 0., 0., 0.],\n",
       "        [0., 0., 0.,  ..., 0., 0., 0.],\n",
       "        [0., 0., 0.,  ..., 0., 0., 0.]], device='cuda:0', dtype=torch.bfloat16)"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "zero_grad()\n",
    "p.grad"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
